Parallel.For():在循环外更新变量

时间:2010-05-05 14:37:54

标签: c# .net .net-4.0 parallel-processing

我只是在研究新的.NET 4.0功能。有了这个,我正在尝试使用Parallel.For和普通for(x;x;x)循环进行简单计算。

然而,我在50%的时间里得到了不同的结果。

long sum = 0;

Parallel.For(1, 10000, y =>
    {
        sum += y;
    }
);

Console.WriteLine(sum.ToString());

sum = 0;

for (int y = 1; y < 10000; y++)
{
   sum += y;
}
Console.WriteLine(sum.ToString());

我的猜测是线程正试图同时更新“sum” 有明显的方法吗?

7 个答案:

答案 0 :(得分:69)

你不能这样做。并行线程正在共享sum。您需要确保sum变量一次只能被一个线程访问:

// DON'T DO THIS!
Parallel.For(0, data.Count, i =>
{
    Interlocked.Add(ref sum, data[i]);
});

但是......这是一种反模式,因为你已经有效地序列化了循环,因为每个线程都会锁定Interlocked.Add

你需要做的是添加小计并在最后合并它们:

Parallel.For<int>(0, result.Count, () => 0, (i, loop, subtotal) =>
    {
        subtotal += result[i];
        return subtotal;
    },
    (x) => Interlocked.Add(ref sum, x)
);

您可以在MSDN上找到对此进一步的讨论:http://msdn.microsoft.com/en-us/library/dd460703.aspx

PLUG:您可以在第2章A Guide to Parallel Programming

中找到更多相关信息。

以下也绝对值得一读...

Patterns for Parallel Programming: Understanding and Applying Parallel Patterns with the .NET Framework 4 - Stephen Toub

答案 1 :(得分:17)

sum += y;实际上是sum = sum + y;。由于以下竞争条件,您得到的结果不正确:

  1. Thread1读取sum
  2. Thread2读取sum
  3. Thread1计算sum+y1,并将结果存储在sum
  4. Thread2计算sum+y2,并将结果存储在sum
  5. sum现在等于sum+y2,而不是sum+y1+y2

答案 2 :(得分:5)

你的猜测是正确的。

当您编写sum += y时,运行时会执行以下操作:

  1. 将字段读入堆栈
  2. y添加到堆栈
  3. 将结果写回字段
  4. 如果两个线程同时读取该字段,则第一个线程所做的更改将被第二个线程覆盖。

    您需要使用Interlocked.Add,它将执行添加为单个原子操作。

答案 3 :(得分:4)

增加长度不是原子操作。

答案 4 :(得分:4)

我认为区分这个循环不能被分区用于并行是很重要的,因为如上所述,循环的每次迭代都依赖于先验。 parallel for设计用于显式并行任务,例如像素缩放等,因为循环的每次迭代都不能在迭代之外具有数据依赖性。

Parallel.For(0, input.length, x =>
{
    output[x] = input[x] * scalingFactor;
});

上面的代码示例允许轻松划分并行性。然而一句警告,并行性带来了成本,即使我上面用作例子的循环也太简单了,因为设置时间比通过并行性节省的时间要长。

答案 5 :(得分:3)

似乎没有人提到过一个重点:对于数据并行操作(例如OP),使用PLINQ而不是Parallel通常更好(在效率和简单性方面)类。 OP的代码实际上很难并行化:

long sum = Enumerable.Range(1, 10000).AsParallel().Sum();

以上代码段使用ParallelEnumerable.Sum方法,但也可以将Aggregate用于更一般的方案。有关这些方法的说明,请参阅Parallel Loops章节。

答案 6 :(得分:-1)

如果此代码中有两个参数。 例如

long sum1 = 0;
long sum2 = 0;

Parallel.For(1, 10000, y =>
    {
        sum1 += y;
        sum2=sum1*y;
    }
);

我们会怎么做?我猜这必须使用数组!