使用Parallel.ForEach得到不同的求和结果

时间:2010-07-29 21:58:34

标签: c# parallel-processing task

我有一个foreach循环,我正在并行化,我注意到一些奇怪的东西。代码看起来像

double sum = 0.0;

Parallel.ForEach(myCollection, arg =>
{
     sum += ComplicatedFunction(arg);
});

// Use sum variable below

当我使用常规foreach循环时,我会得到不同的结果。 ComplicatedFunction内部可能存在更深层次的内容,但sum变量可能会受到并行化的意外影响吗?

4 个答案:

答案 0 :(得分:30)

  

并行化可能会对sum变量产生意外影响吗?

是。
double的访问不是原子的,sum += ...操作永远不是线程安全的,即使对于原子类型也是如此。所以你有多种竞争条件,结果是不可预测的。

您可以使用以下内容:

double sum = myCollection.AsParallel().Sum(arg => ComplicatedFunction(arg));

或者,用更短的符号

double sum = myCollection.AsParallel().Sum(ComplicatedFunction);

答案 1 :(得分:11)

与上面提到的其他答案一样,从多个线程(这是Parallel.ForEach所做的)更新sum变量不是线程安全的操作。在进行更新之前获取锁定的简单修复将修复 问题。

double sum = 0.0;
Parallel.ForEach(myCollection, arg => 
{ 
  lock (myCollection)
  {
    sum += ComplicatedFunction(arg);
  }
});

但是,这引入了另一个问题。由于在每次迭代时获取锁,因此这意味着每次迭代的执行将被有效地序列化。换句话说,最好只使用普通的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;
        }
    });

答案 2 :(得分:4)

如果你认为sum += ComplicatedFunction实际上由一堆操作组成,请说:

r1 <- Load current value of sum
r2 <- ComplicatedFunction(...)
r1 <- r1 + r2

所以现在我们随机交错两个(或更多)并行实例。一个线程可能持有用于执行其计算的总和的陈旧“旧值”,其结果是在一些修改版本的sum之上写回。这是一种经典的竞争条件,因为根据交错的方式,某些结果会以不确定的方式丢失。

答案 3 :(得分:-1)

或者您可以使用.Net中正确定义的并行聚合操作。这是代码

        object locker = new object();
        double sum= 0.0;
        Parallel.ForEach(mArray,
                        () => 0.0,                 // Initialize the local value.
                        (i, state, localResult) => localResult + ComplicatedFunction(i), localTotal =>   // Body delegate which returns the new local total.                                                                                                                                           // Add the local value
                            {
                                lock (locker) sum4+= localTotal;
                            }    // to the master value.
                        );