Ontario Ontario - 3 months ago 14
C# Question

Unexpected behavior of lock in C# with Parallel.For

doing the following

static object aggLock = new object();
static long max = 10000000;

static void Main(string[] args)
{
double totalSumSeq = 0;
double totalSumLock = 0;

// Seq
for (int i = 0; i < max; i++)
{
double y = Math.Sqrt(i);
totalSumSeq += y;
}

...
}


returns the expected 21,081,849,486.4393.

Using

// Parallel.For(from, to, init, body, finally);
Parallel.For(0, max, () => 0.0, (i, pls, y) => // (LoopVariable, ParallelLoopState, ThreadLocalVariable)
{
y = Math.Sqrt(i);
return y;
},
partSum =>
{
lock (aggLock)
{
totalSumLock += partSum;
}
}
);


instead I get totally different values, like in race conditions. Why?

Thanks
Ontario

Answer

You should aggregate the partial sums into y:

Parallel.For(0, max, () => 0.0, (i, pls, y) => 
 {
    //y = Math.Sqrt(i);
    y += Math.Sqrt(i);  // += to  fix it
    return y;
 }, ...

y is initialized to 0.0 with () => 0.0 and it resurfaces at the end of a partition as partSum. But you only used the last value of the partition.

An alternative using PLinq (but Range() won't accept long for max) :

        double plinqSum = Enumerable
            .Range(0, (int) max)
            .AsParallel()
            .Sum(i => Math.Sqrt(i));  // or just  .Sum(Math.Sqrt);

This .AsParallel().Sum() bit is essentially what you're building with the Parallel.For()