并行ForEach在不同的Ocasions中给出不同的结果

问题描述:

我从来没有用过Parallel.ForEach但我玩过它并发现了这种情况。并行ForEach在不同的Ocasions中给出不同的结果

我运行并行循环与枚举范围首先(0,1)然后(0,1,2)然后我再次运行第二个是睡着了线程后(代码MSDN上https://msdn.microsoft.com/en-us/library/dd997393(v=vs.110).aspx发现并与subtotal *=2编辑试图理解它在做什么)为200毫秒,并且将结果然后不同

如果Thread.sleep(200)没有被注释掉这是结果

result 1 = 2 
result 2 = 6 
result 3 = 4 

如果Thread.sleep(200)被注释掉是这种结果

result 1 = 2 
result 2 = 6 
result 3 = 6 

下面是代码

Stopwatch timer = new Stopwatch(); 
int[] nums = Enumerable.Range(0, 1).ToArray(); 
long total = 0; 
for (int i = 0; i < 2; i++) 
{ 
    timer.Restart(); 
    total = 0; 
    if (i == 0) nums = Enumerable.Range(0, 1).ToArray(); 
    if (i == 1) nums = Enumerable.Range(0, 2).ToArray(); 
    Parallel.ForEach<int, long>(nums,() => 0,(j, loop, subtotal) => 
    { 
     subtotal += 1; 
     subtotal *= 2; 
     return subtotal; 
    },(finalResult) => Interlocked.Add(ref total, finalResult)); 

    Console.WriteLine("The total from Parallel.ForEach is {0:N0} and took {1}", total, timer.Elapsed); 
    timer.Stop(); 
    //Thread.Sleep(200); 
} 

timer.Restart(); 
nums = Enumerable.Range(0, 2).ToArray(); 
total = 0; 
Parallel.ForEach<int, long>(nums,() => 0, (j, loop, subtotal) => 
{ 
    subtotal += 1; 
    subtotal *= 2; 
    return subtotal; 
},(finalResult) => Interlocked.Add(ref total, finalResult)); 

Console.WriteLine("The total from Parallel.ForEach is {0:N0} and took {1}", total, timer.Elapsed); 
timer.Stop(); 

我想它已经得到了与工作在彼此线程做,但这似乎是一个错误

注意我也看看Simulation gives different result with normal for loop Vs Parallel For
这是怎么发生的?

+1

@JánЯabčan你能否认罪详细阐述。我不明白这个代码是在什么时候乘以2,为什么? – Neil

+0

我甚至不知道如何得到你的结果... – sam

+0

@sam改变你的thread.sleep为1000,看看你是否得到它然后 – Neil

因为这个代码是不明确的:

Parallel.ForEach<int, long>(nums,() => 0,(j, loop, subtotal) => 
{ 
    subtotal += 1; 
    subtotal *= 2; 
    return subtotal; 
},(finalResult) => Interlocked.Add(ref total, finalResult)); 

在这方面,如果线程执行两个迭代,那么你得到的结果6.实际上,你这样做:

subTotal = 0; //From init 
subTotal += 1; //=1 First iteration 
subTotal *= 2; //=2 First iteration 
subTotal += 1; //=3 Second iteration 
subTotal *= 2; //=6 Second iteration 
total += subTotal; //=6 End gathering (actually interlocked) 

但是,如果两个线程共享工作,你会得到

subTotal1 = 0; //From init 
subTotal2 = 0; //From init 
subTotal2 += 1; //=1 
subTotal1 += 1; //=1 
subTotal1 *= 2; //=2 
subTotal2 *= 2; //=2 
total += subTotal1 //=2 End gathering 1 (interlocked) 
total += subTotal2 //=4 End gathering 2 (interlocked) 
+0

谢谢您的解释。所以只是为了确保我正确地理解这个并行foreach永远不应该用于计算,然后在每次迭代都有自己的执行而不是用于发送到下一次迭代的值时使用它。 – Neil

+1

@尼尔 - 我不确定你的问题是什么。但是,如果您使用的是使用本地状态的“Parallel.ForEach”变体,这个函数会改变本地状态,并且最终的操作会收集所有结果并合并它们,但如果您获得所有结果无论有多少线程正在运行(因此将存在多少本地状态副本)以及结果如何在最终操作中合并,都会产生一致的结果。你有一个计算结果不能这样工作 - 这对于使用多少个线程是至关重要的,因此存在多少个本地状态副本。 –

+0

这就是我问的:)。感谢您的回答和澄清。最后通过投票表决并接受 – Neil