Parallel.Foreach 循环得到的结果与 For 循环不同?
Parallel.Foreach loop gets different result than For loop?
我已经
编写简单的 for 循环迭代数组和 Parallel.ForEach 循环做同样的事情。但是,我得到的结果不同,所以我想问问到底发生了什么? :D
class Program
{
static void Main(string[] args)
{
long creating = 0;
long reading = 0;
long readingParallel = 0;
for (int j = 0; j < 10; j++)
{
Stopwatch timer1 = new Stopwatch();
Random rnd = new Random();
int[] array = new int[100000000];
timer1.Start();
for (int i = 0; i < 100000000; i++)
{
array[i] = rnd.Next(5);
}
timer1.Stop();
long result = 0;
Stopwatch timer2 = new Stopwatch();
timer2.Start();
for (int i = 0; i < 100000000; i++)
{
result += array[i];
}
timer2.Stop();
Stopwatch timer3 = new Stopwatch();
long result2 = 0;
timer3.Start();
Parallel.ForEach(array, (item) =>
{
result2 += item;
});
if (result != result2)
{
Console.WriteLine(result + " - " + result2);
}
timer3.Stop();
creating += timer1.ElapsedMilliseconds;
reading += timer2.ElapsedMilliseconds;
readingParallel += timer3.ElapsedMilliseconds;
}
Console.WriteLine("Create : \t" + creating / 100);
Console.WriteLine("Read: \t\t" + reading / 100);
Console.WriteLine("ReadP: \t\t" + readingParallel / 100);
Console.ReadKey();
}
}
所以在这种情况下我得到了结果:
结果 = 200009295;
结果 2 = 35163054;
有什么问题吗?
+=
运算符是非原子的,实际上执行多个操作:
- 将
result
指向的位置的值加载到内存中
- 将
array[i]
添加到内存中的值(我在这里简化)
- 将结果写回
result
由于许多这些添加操作将 运行 并行,这不仅是可能的,而且很可能在其中一些操作之间存在竞争,其中一个线程读取 result
值并执行加法,但在它有机会写回之前,另一个线程获取旧的 result
值(尚未更新)并执行加法。然后两个线程将各自的值写入result
。不管哪一个赢得比赛,你最终得到的数字都比预期的要少。
这就是 Interlocked
class 存在的原因。
您的代码很容易修复:
Parallel.ForEach(array, (item) =>
{
Interlocked.Add(ref result2, item);
});
如果 Parallel.ForEach
在这种情况下最终比完全同步版本慢,请不要感到惊讶。这是因为
- 您传递给
Parallel.ForEach
的委托内部的工作量 非常 很小
Interlocked
方法会产生轻微但不可忽略的开销,在这种特殊情况下会非常明显
我已经 编写简单的 for 循环迭代数组和 Parallel.ForEach 循环做同样的事情。但是,我得到的结果不同,所以我想问问到底发生了什么? :D
class Program
{
static void Main(string[] args)
{
long creating = 0;
long reading = 0;
long readingParallel = 0;
for (int j = 0; j < 10; j++)
{
Stopwatch timer1 = new Stopwatch();
Random rnd = new Random();
int[] array = new int[100000000];
timer1.Start();
for (int i = 0; i < 100000000; i++)
{
array[i] = rnd.Next(5);
}
timer1.Stop();
long result = 0;
Stopwatch timer2 = new Stopwatch();
timer2.Start();
for (int i = 0; i < 100000000; i++)
{
result += array[i];
}
timer2.Stop();
Stopwatch timer3 = new Stopwatch();
long result2 = 0;
timer3.Start();
Parallel.ForEach(array, (item) =>
{
result2 += item;
});
if (result != result2)
{
Console.WriteLine(result + " - " + result2);
}
timer3.Stop();
creating += timer1.ElapsedMilliseconds;
reading += timer2.ElapsedMilliseconds;
readingParallel += timer3.ElapsedMilliseconds;
}
Console.WriteLine("Create : \t" + creating / 100);
Console.WriteLine("Read: \t\t" + reading / 100);
Console.WriteLine("ReadP: \t\t" + readingParallel / 100);
Console.ReadKey();
}
}
所以在这种情况下我得到了结果: 结果 = 200009295; 结果 2 = 35163054;
有什么问题吗?
+=
运算符是非原子的,实际上执行多个操作:
- 将
result
指向的位置的值加载到内存中 - 将
array[i]
添加到内存中的值(我在这里简化) - 将结果写回
result
由于许多这些添加操作将 运行 并行,这不仅是可能的,而且很可能在其中一些操作之间存在竞争,其中一个线程读取 result
值并执行加法,但在它有机会写回之前,另一个线程获取旧的 result
值(尚未更新)并执行加法。然后两个线程将各自的值写入result
。不管哪一个赢得比赛,你最终得到的数字都比预期的要少。
这就是 Interlocked
class 存在的原因。
您的代码很容易修复:
Parallel.ForEach(array, (item) =>
{
Interlocked.Add(ref result2, item);
});
如果 Parallel.ForEach
在这种情况下最终比完全同步版本慢,请不要感到惊讶。这是因为
- 您传递给
Parallel.ForEach
的委托内部的工作量 非常 很小 Interlocked
方法会产生轻微但不可忽略的开销,在这种特殊情况下会非常明显