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

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循环时,我会得到不同的结果.内部可能存在更深层次的内容,ComplicatedFunctionsum变量可能会受到并行化的意外影响?

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)


Gia*_*ian 5

如果您认为sum += ComplicatedFunction它实际上是由一系列操作组成,请说:

r1 <- Load current value of sum
r2 <- ComplicatedFunction(...)
r1 <- r1 + r2
Run Code Online (Sandbox Code Playgroud)

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

  • 你是对的,但实际上情况比你说的要糟糕得多。不仅仅是加载、计算和存储操作不是原子的。甚至不能保证访问 double 中的 *bits* 是原子的!C# 规范仅保证访问 32 位(和更小)数字类型和引用是原子的。双打是 64 位,因此不能保证是原子的。该程序可以实现为: r1 &lt;-- 加载和的前 32 位,r1 &lt;-- 加载和的后 32 位...意味着可以在复制双精度的 *一半* 时交错操作。 (6认同)