753

public int counterクラスに複数のスレッドがアクセスするフィールドがあるとしましょう。これintはインクリメントまたはデクリメントされるだけです。

このフィールドをインクリメントするには、どのアプローチを使用する必要がありますか、またその理由は何ですか?

  • lock(this.locker) this.counter++;
  • Interlocked.Increment(ref this.counter);
  • のアクセス修飾子をに変更しcounterますpublic volatile

私が発見したvolatileので、私は多くのlockステートメントとの使用を削除してきましたInterlocked。しかし、これを行わない理由はありますか?

4

10 に答える 10

955

最悪(実際には機能しません)

のアクセス修飾子をに変更し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 では、これは文字通り単一の CPU 命令であることがよくあります)。

ただし、他のCPUの並べ替えを回避するかどうか、または揮発性とインクリメントを組み合わせる必要があるかどうかは完全にはわかりません。

インターロック注記:

  1. インターロックされた方法は、任意の数のコアまたは CPU で同時に安全です。
  2. インターロックされたメソッドは、実行する命令の周囲に完全なフェンスを適用するため、並べ替えは発生しません。
  3. インターロックされたメソッドは、 volatile フィールドへのアクセスを必要としないか、サポートしません。 volatile は、特定のフィールドの操作の周りにハーフ フェンスを配置し、インターロックはフル フェンスを使用するためです。

脚注: volatile が実際に役立つもの。

このvolatile種のマルチスレッドの問題を防げないのに、何のためにあるのでしょうか? 良い例は、2 つのスレッドがあるとします。1 つは常に変数 (たとえばqueueLength) に書き込み、もう 1 つは常に同じ変数から読み取ります。

が揮発性でない場合queueLength、スレッド A は 5 回書き込みを行う可能性がありますが、スレッド B はそれらの書き込みが遅延していると見なす可能性があります (または、場合によっては間違った順序である可能性さえあります)。

解決策はロックすることですが、この状況では volatile を使用することもできます。これにより、スレッド B は、スレッド A が書いた最新のものを常に見ることができます。ただし、このロジックは、読み取りをまったく行わないライターと書き込みを行わないリーダーがいて、書き込みが原子値である場合にのみ機能することに注意てください。単一の読み取り-変更-書き込みを行うとすぐに、インターロック操作に移動するか、ロックを使用する必要があります。

于 2008-09-30T20:13:08.237 に答える
157

編集:コメントで述べたように、最近では、明らかに問題ない単一の変数Interlockedの場合に喜んで使用しています。もっと複雑になると、ロックに戻ります...

読み取りと書き込みは別の命令であるため、インクリメントする必要がある場合は使用volatileできません。読み取り後、書き戻す前に、別のスレッドが値を変更する可能性があります。

個人的には、ほとんどの場合、ロックするだけです。ボラティリティや Interlocked.Increment よりも、明らかに正しい方法で正しく行う方が簡単です。私に関する限り、ロックフリー マルチスレッディングは真のスレッディング エキスパート向けであり、私はその 1 人ではありません。Joe Duffy と彼のチームが、私が構築するものほど多くのロックを必要とせずに物事を並列化する優れたライブラリを構築するなら、それはすばらしいことです。複雑にしないでおく。

于 2008-09-30T19:29:35.003 に答える
47

" " は!volatileを置き換えません。Interlocked.Increment変数がキャッシュされていないことを確認するだけで、直接使用されます。

変数をインクリメントするには、実際には 3 つの操作が必要です。

  1. 読んだ
  2. インクリメント
  3. 書きます

Interlocked.Increment3 つの部分すべてを単一のアトミック操作として実行します。

于 2008-09-30T19:32:52.327 に答える
44

ロックまたはインターロックされた増分のいずれかが探しているものです。

揮発性は間違いなくあなたが求めているものではありません.現在のコードパスでコンパイラがメモリからの読み取りを最適化できる場合でも、変数を常に変化するものとして扱うようにコンパイラに指示するだけです。

例えば

while (m_Var)
{ }

別のスレッドで m_Var が false に設定されているが、それが volatile として宣言されていない場合、コンパイラは、CPU レジスタに対してチェックするようにすることで、自由に無限ループにすることができます (ただし、常にそうなるとは限りません)。 m_Var のメモリ位置に別の読み取りを発行する代わりに (これはキャッシュされている可能性があります - 私たちは知りませんし、気にしません。それが x86/x64 のキャッシュ コヒーレンシのポイントです)。命令の並べ替えについて言及した他のユーザーによる以前のすべての投稿は、単に x86/x64 アーキテクチャを理解していないことを示しています。揮発性はありませ「並べ替えを防ぐ」という以前の投稿で示唆されているように、読み取り/書き込みバリアを発行します。実際、MESI プロトコルのおかげで、実際の結果が物理メモリにリタイアされたか、単にローカル CPU のキャッシュに存在するかに関係なく、読み取った結果は CPU 間で常に同じであることが保証されています。これについて詳しくは説明しませんが、これがうまくいかない場合、Intel/AMD がプロセッサのリコールを発行する可能性が高いのでご安心ください。これはまた、順不同の実行などを気にする必要がないことも意味します。結果は常に順番どおりにリタイアすることが保証されています。

インターロック インクリメントを使用すると、プロセッサは外に出て、指定されたアドレスから値をフェッチし、インクリメントして書き戻す必要があります。これはすべて、キャッシュ ライン全体の排他的所有権 (ロック xadd) を保持して、他のプロセッサが変更できないようにするためです。その価値。

volatile を使用しても、最終的には 1 つの命令だけになります (JIT が効率的であると仮定すると) - inc dword ptr [m_Var]。ただし、プロセッサ (cpuA) は、インターロック バージョンで行ったすべての処理を実行している間、キャッシュ ラインの排他的所有権を要求しません。ご想像のとおり、これは、cpuA によって読み取られた後、他のプロセッサが更新された値を m_Var に書き戻すことができることを意味します。したがって、値を 2 回インクリメントする代わりに、1 回だけになります。

これで問題が解決することを願っています。

詳細については、「マルチスレッド アプリにおけるロー ロック技術の影響を理解する」を参照してください - http://msdn.microsoft.com/en-au/magazine/cc163715.aspx

ps この非常に遅い返信の原因は何ですか? すべての返信は明らかに間違っていました (特に、回答としてマークされたもの) が説明されていたので、これを読んでいる他の人のためにそれを片付けなければなりませんでした。肩をすくめる

pps ターゲットは x86/x64 であり、IA64 ではないと想定しています (メモリ モデルが異なります)。Microsoft の ECMA 仕様は、最強のメモリ モデルではなく、最も弱いメモリ モデルを指定しているという点で台無しになっていることに注意してください (プラットフォーム間で一貫性を保つために、最強のメモリ モデルに対して指定する方が常に良いです。そうしないと、x86/ x64 は IA64 ではまったく動作しない可能性がありますが、Intel は IA64 用に同様に強力なメモリ モデルを実装しています) - Microsoft 自身もこれを認めています - http://blogs.msdn.com/b/cbrumme/archive/2003/05/17/51445.aspx .

于 2011-06-23T15:08:37.957 に答える
18

連動機能はロックしません。これらはアトミックです。つまり、インクリメント中にコンテキスト スイッチが発生する可能性なしに完了することができます。したがって、デッドロックや待機の可能性はありません。

ロックとインクリメントよりも常にそれを好むべきだと思います。

Volatile は、あるスレッドでの書き込みを別のスレッドで読み取る必要がある場合、およびオプティマイザーが変数の操作を並べ替えないようにしたい場合に役立ちます (オプティマイザーが認識していない別のスレッドで発生しているため)。これは、インクリメントする方法とは直交する選択です。

これは、ロックフリー コードと、それを書くための正しい方法について詳しく知りたい場合に、非常に優れた記事です。

http://www.ddj.com/hpc-high-performance-computing/210604448

于 2008-09-30T19:27:22.403 に答える
13

lock(...) は機能しますが、スレッドをブロックする可能性があり、他のコードが互換性のない方法で同じロックを使用している場合、デッドロックを引き起こす可能性があります。

Interlocked.* はそれを行う正しい方法です...最新のCPUはこれをプリミティブとしてサポートしているため、オーバーヘッドがはるかに少なくなります。

volatile 自体は正しくありません。変更された値を取得して書き戻そうとするスレッドは、同じことを行う別のスレッドと競合する可能性があります。

于 2008-09-30T19:32:09.473 に答える
8

私はJon Skeetの答えに二番目で、「揮発性」とインターロックについてもっと知りたい人のために次のリンクを追加したいと思います:

原子性、揮発性、および不変性は異なります、パート 1 - (Eric Lippert の Fabulous Adventures In Coding)

原子性、揮発性、不変性は異なる、パート 2

原子性、揮発性、不変性は異なる、パート 3

Sayonara Volatile - (2012 年に登場した Joe Duffy のウェブログの Wayback Machine スナップショット)

于 2012-04-29T22:04:53.253 に答える