2016-12-30 11 views
3

私はpの例を見ています。ここでロックが必要なのはなぜですか?

// Note: this is not the most efficient implementation. 
// This is just an example of using a lock to protect shared state. 
static int ParallelSum(IEnumerable<int> values) 
{ 
    object mutex = new object(); 
    int result = 0; 
    Parallel.ForEach(source: values, 
     localInit:() => 0, 
     body: (item, state, localValue) => localValue + item, 
     localFinally: localValue => 
     { 
      lock (mutex) 
       result += localValue; 
     }); 
    return result; 
} 

あるとlockが必要な理由私は少し混乱しているステファン・クリアリー氏の著書の40。たとえば、int、つまり{1, 5, 6}の集計を合計する場合は、共有合計resultを任意の順序でインクリメントする必要はありません。

(1 + 5) + 6 = 1 + (5 + 6) = (1 + 6) + 5 = ... 

誰かがここで私の考えが間違っていると説明できますか?

私は、メソッドの本体によって混乱し、ほとんどが単純な追加など

int result = 0; 
Parallel.ForReach(values, (val) => { result += val; }); 
return result; 

答えて

2
に触れる前に、2つのスレッドが別のスレッドのUDPATEが表示されないことがあり

result += localValue;というステートメントは、実際にはresult = result + localValue;と言っています。異なるスレッドが共有するリソース(変数)を読み書きしています。これは簡単にレースコンディションにつながる可能性があります。 lockは、指定された瞬間にこのステートメントが単一スレッドによってアクセスされるようにします。

10

操作がアトミックではありませんので、スレッドセーフではありませんすることはできませんよね。このコード例では、ロックを省略した場合、2つの加算演算がほぼ同時に実行されるため、値の1つがオーバーライドされたり間違って追加される可能性があります。 のスレッドセーフで整数をインクリメントする方法があります。Interlocked.Add(int, int)です。しかし、これは使用されていないので、例のコードでは、せいぜい1つの非原子加算演算が一度に完了するように(シーケンスで、並列ではない)確実にロックする必要があります。

+0

私は問題を引き起こすその代入演算子を考える。それは自己ではありません。 –

+6

@ M.kazemAkhgaryまた正しくない。割り当てと追加の両方は、それぞれ独立してアトミックです。 *が原子ではないのは、インクリメントです。このインクリメントは、読み込み、追加、割り当てから構成されます。それぞれの3つの操作は100%の原子ですが、3つとも*は原子ではありません。 – Servy

7

「結果」が、それを更新するそのrace conditionを更新する順序について、そのない、そのオペレータ+=を覚えてはアトミックではありませんので、彼らはそれ

+0

それはCPUがこの衝突を起こさせることさえあると私にはとても奇妙に思えます。私がC#を書くとき、ハードウェアレベルのバグを考えなければならないとは思えません。私はすべきだと思います。 – user7127000

+0

してください! CやC++とあまり違いはありません –

+2

@ user7127000これは*バグではありません。不可欠な機能です。 CPUが操作を並べ替えることを許可されていない場合、コンピュータは何百何千倍も遅くなります。これらのスレッドは、スレッド間の共有状態が処理するのが非常に難しいので、ここでの例を含め、すべてのコストをかけずにコードを実行する必要はありません複数のスレッドではなく、単一のスレッドで実行する方が高速です。 – Servy

関連する問題