Search code examples
c#parallel-processingtask-parallel-libraryparallel.for

Understanding Parallel.For Loop and local variables


I am new in the parallel computing and I'm with some problems running a Parallel.For in C#. I'm trying visit multiple Web Sites in simultaneous, get the HTML and register them in multiple SQLite Database. Everything seems work fine until I check the results more precisely. I noticed that in one loop for 0 to 20, the code entered 20 times in the shared part of loop and only 16 times in local part. So, was missing 4 results. To understand the problem I made a experience where I only put two counters. One in the global part and another in the local. The output of global count was 20 and in the local part 1! After that I put a 2 seconds sleep before the returning of global part to the local part. In this case the output of global count was 20 and in the local part was 13! Can you explain me what I'm doing wrong?

static void ParalellCalc()
{
    var tm = new Stopwatch();
    tm.Start();
    int count = 0;
    int count2 = 0;
    var parl = Parallel.For<int>(0, 20,
        new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount},
        () => 0, (i, state, Enrada) =>
    {
        count++;
        Thread.Sleep(2000);
        return Enrada;
    },
    (x) =>
    {
        count2++;
    });

    tm.Stop();
    Console.WriteLine(tm.Elapsed);
    Console.WriteLine("Global: " + count.ToString());
    Console.WriteLine("Local: " + count2.ToString());
    Console.WriteLine(tm.Elapsed);
    tm.Reset();
}

EDIT: I go into your suggestions and I made the same example with the Interlocked.Increment to increment the counters. The produced results are exactly the same. If I remove the Thread.Sleep(2000) the second counter produce the result of 1!? If I don't remove produce the result of 16. The first counter display in all the cases the value of 20 as should be. Anyone can explain that?

static void ParalellCalc()
{
    var tm = new Stopwatch();
    tm.Start();
    int count = 0;
    int count2 = 0;
    var parl = Parallel.For<int>(0, 20,
        new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount},
        () => 0, (i, state, Enrada) =>
    {
        Interlocked.Increment(ref count);
        return Enrada;
    },
    (x) =>
    {
        Interlocked.Increment(ref count2);
    });

    tm.Stop();
    Console.WriteLine(tm.Elapsed);
    Console.WriteLine("Global: " + count.ToString());
    Console.WriteLine("Local: " + count2.ToString());
    Console.WriteLine(tm.Elapsed);
    tm.Reset();
}

Solution

  • The Parallel.For method parallelizes the workload by splitting it into partitions. The number of partitions and the size of each partition is determined by internal heuristics. Your experiment demonstrates that a workload of 20 items can be split in only 1 partition, or in 16 partitions, depending in the duration of the processing of each item. By adding the line Thread.Sleep(2000) you are changing your workload from extremely light to quite heavy, and so more partitions are created to balance the workload. These 16 partitions are typically processed by fewer than 16 threads, because each thread processes more than one partitions.

    For a better understanding of how Parallel.For works you should log more info than the two counters count and count2. You should also have one counter for each thread, using a ConcurrentDictionary<int, int> with keys the ID of each thread.