我只是在研究新的.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” 有明显的方法吗?
答案 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
中找到更多相关信息。以下也绝对值得一读...
答案 1 :(得分:17)
sum += y;
实际上是sum = sum + y;
。由于以下竞争条件,您得到的结果不正确:
sum
sum
sum+y1
,并将结果存储在sum
sum+y2
,并将结果存储在sum
sum
现在等于sum+y2
,而不是sum+y1+y2
。
答案 2 :(得分:5)
你的猜测是正确的。
当您编写sum += y
时,运行时会执行以下操作:
y
添加到堆栈如果两个线程同时读取该字段,则第一个线程所做的更改将被第二个线程覆盖。
您需要使用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;
}
);
我们会怎么做?我猜这必须使用数组!