Bri*_*ett 18 c# parallel-processing task
我有一个foreach循环,我正在并行化,我注意到一些奇怪的东西.代码看起来像
double sum = 0.0;
Parallel.ForEach(myCollection, arg =>
{
sum += ComplicatedFunction(arg);
});
// Use sum variable below
Run Code Online (Sandbox Code Playgroud)
当我使用常规foreach循环时,我会得到不同的结果.内部可能存在更深层次的内容,ComplicatedFunction但sum变量可能会受到并行化的意外影响?
Hen*_*man 30
并行化可能会对sum变量产生意外影响吗?
是.
对a的访问double不是原子的,sum += ...操作永远不是线程安全的,即使对于原子类型也是如此.所以你有多种竞争条件,结果是不可预测的.
你可以使用类似的东西:
double sum = myCollection.AsParallel().Sum(arg => ComplicatedFunction(arg));
Run Code Online (Sandbox Code Playgroud)
或者,用较短的符号表示
double sum = myCollection.AsParallel().Sum(ComplicatedFunction);
Run Code Online (Sandbox Code Playgroud)
Bri*_*eon 11
与提到的其他答案一样,sum从多个线程更新变量(这是Parallel.ForEach所做的)不是线程安全的操作.在进行更新之前获取锁定的简单修复将解决该问题.
double sum = 0.0;
Parallel.ForEach(myCollection, arg =>
{
lock (myCollection)
{
sum += ComplicatedFunction(arg);
}
});
Run Code Online (Sandbox Code Playgroud)
然而,这引入了另一个问题.由于在每次迭代时获取锁,因此这意味着每次迭代的执行将被有效地序列化.换句话说,最好只使用一个普通的旧foreach循环.
现在,实现这一目标的诀窍是将问题分成独立且独立的卡盘.幸运的是,当你想要做的就是对迭代的结果求和时,这是非常容易的,因为求和操作是可交换的和关联的,并且因为迭代的中间结果是独立的.
所以这就是你如何做到的.
double sum = 0.0;
Parallel.ForEach(myCollection,
() => // Initializer
{
return 0D;
},
(item, state, subtotal) => // Loop body
{
return subtotal += ComplicatedFunction(item);
},
(subtotal) => // Accumulator
{
lock (myCollection)
{
sum += subtotal;
}
});
Run Code Online (Sandbox Code Playgroud)
如果您认为sum += ComplicatedFunction它实际上是由一系列操作组成,请说:
r1 <- Load current value of sum
r2 <- ComplicatedFunction(...)
r1 <- r1 + r2
Run Code Online (Sandbox Code Playgroud)
所以现在我们随机交错两个(或更多)并行实例。一个线程可能持有 sum 的陈旧“旧值”,用于执行其计算,其结果写回 sum 的某个修改版本的顶部。这是一种经典的竞争条件,因为基于交错的完成方式,某些结果会以不确定的方式丢失。