.NET4.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
は並列スレッド間で共有されています。 sum
変数が一度に1つのスレッドによってのみアクセスされていることを確認する必要があります。
// 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
プラグ:これについて詳しくは、第2章 並列プログラミングガイド を参照してください。
以下も間違いなく読む価値があります...
並列プログラミングのパターン:.NET Framework 4を使用した並列パターンの理解と適用-StephenToub
sum += y;
は実際にはsum = sum + y;
です。次の競合状態が原因で、誤った結果が得られます。
sum
を読み取りますsum
を読み取りますsum+y1
を計算し、結果をsum
に格納しますsum+y2
を計算し、結果をsum
に格納しますsum
は、sum+y2
ではなくsum+y1+y2
と等しくなりました。
あなたの推測は正しいです。
あなたが書くときsum += y
、ランタイムは次のことを行います。
y
を追加します2つのスレッドが同時にフィールドを読み取る場合、最初のスレッドによって行われた変更は2番目のスレッドによって上書きされます。
使用する必要があります Interlocked.Add
、単一のアトミック操作として加算を実行します。
上で述べたように、ループの各反復は前に依存しているため、このループは並列処理のために分割できないことを区別することが重要だと思います。 parallel forは、ピクセルスケーリングなどの明示的に並列なタスク用に設計されています。これは、ループの各反復が、その反復以外のデータ依存関係を持つことができないためです。
Parallel.For(0, input.length, x =>
{
output[x] = input[x] * scalingFactor;
});
上記の並列処理のための簡単なパーティション分割を可能にするコードの例。ただし、警告の言葉として、並列処理にはコストが伴います。上記の例で使用したループでさえ、並列処理によって節約された時間よりもセットアップ時間が長くなるため、並列処理を気にするのは非常に単純すぎます。
Longをインクリメントすることは、アトミック操作ではありません。
誰も言及していない重要な点:データ並列操作(OPなど)の場合、Parallel
クラスの代わりにPLINQを使用する方が(効率と単純さの両方の観点から)多くの場合優れています。 OPのコードは、実際には並列化するのは簡単です。
long sum = Enumerable.Range(1, 10000).AsParallel().Sum();
上記のスニペットは ParallelEnumerable.Sum
メソッド。ただし、より一般的なシナリオでは Aggregate
を使用することもできます。これらのアプローチの説明については、 並列ループ の章を参照してください。