クラスにpublic int counter
複数のスレッドからアクセスされるフィールドがあるとします。これはint
増分または減分のみ行われます。
このフィールドを増やすには、どのアプローチを使用する必要がありますか? また、その理由は何ですか?
lock(this.locker) this.counter++;
、Interlocked.Increment(ref this.counter);
、- のアクセス修飾子
counter
を に変更しますpublic volatile
。
を発見したので、多くの文と の使用をvolatile
削除してきました。しかし、これを行わない理由はあるのでしょうか?lock
Interlocked
ベストアンサー1
最悪(実際には機能しない)
のアクセス修飾子を
counter
次のように変更します。public volatile
他の人が言っているように、これだけでは実際はまったく安全ではありません。重要なのは、volatile
複数の CPU で実行されている複数のスレッドがデータをキャッシュし、命令の順序を変更できるということです。
そうでない場合、 volatile
CPU A が値を増分しても、CPU B は実際にはしばらく経たないとその増分された値を認識しないため、問題が発生する可能性があります。
そうなるとvolatile
、2 つの CPU が同時に同じデータを参照するようになります。ただし、回避しようとしている問題である、読み取り操作と書き込み操作のインターリーブはまったく阻止されません。
次点:
lock(this.locker) this.counter++
;
これは安全に実行できます (lock
にアクセスする他のすべての場所で を忘れないことを条件にthis.counter
)。 これによって、 によって保護されている他のコードを他のスレッドが実行できなくなりますlocker
。 また、ロックを使用すると、上記のようなマルチ CPU の並べ替えの問題も回避できます。これはすばらしいことです。
問題は、ロックが遅いため、locker
実際には関係のない他の場所でロックを再利用すると、他のスレッドが理由もなくブロックされる可能性があることです。
最高
Interlocked.Increment(ref this.counter);
これは安全です。なぜなら、読み取り、増分、書き込みを「1 回のヒット」で実行し、中断されることがないからです。このため、他のコードには影響しませんし、他の場所でロックすることを覚えておく必要もありません。また、非常に高速です (MSDN が言うように、最新の CPU では、これは文字通り 1 つの CPU 命令であることが多いです)。
ただし、他の CPU による順序変更を回避できるかどうか、または増分と volatile を組み合わせる必要があるかどうかは完全にはわかりません。
連動ノート:
- インターロックされたメソッドは、任意の数のコアまたは CPU 上で同時に安全です。
- インターロックされたメソッドは、実行する命令の周囲に完全なフェンスを適用するため、順序の変更は発生しません。
- インターロックされたメソッドは、volatile フィールドへのアクセスを必要としないか、サポートしていません。これは、 volatile が特定のフィールドに対する操作の周囲に半分のフェンスを配置し、インターロックが完全なフェンスを使用するためです。
脚注: 揮発性物質が実際に役立つ理由。
はこのようなマルチスレッドの問題を防止しませんがvolatile
、それは何のためにあるのでしょうか? 良い例としては、2 つのスレッドがあり、1 つは常に変数 ( としますqueueLength
) に書き込み、もう 1 つは常に同じ変数から読み取りを行うとします。
が揮発性でない場合queueLength
、スレッド A は 5 回書き込みを行う可能性がありますが、スレッド B はそれらの書き込みが遅延していると認識する可能性があります (または、順序が間違っている可能性もあります)。
解決策としてはロックが考えられますが、この状況では volatile を使用することもできます。これにより、スレッド B は常にスレッド A が書き込んだ最新の内容を参照できるようになります。ただし、このロジックは、決して読み取らない書き込み側と決して書き込まない読み取り側があり、書き込む内容がアトミック値の場合にのみ機能することに注意してください。単一の読み取り、変更、書き込みを実行するとすぐに、インターロックされた操作に移行するか、ロックを使用する必要があります。