web-dev-qa-db-ja.com

揮発性対ロック対ロック

クラスが複数のスレッドによってアクセスされるpublic int counterフィールドを持っているとしましょう。このintは、増分または減分されるだけです。

この分野を増やすには、どのアプローチを使用する必要がありますか。その理由は何ですか。

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

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

628
core

最悪(実際には動作しません)

counterのアクセス修飾子をpublic volatileに変更します。

他の人々が述べたように、これだけでは実際にはまったく安全ではありません。 volatileのポイントは、複数のCPUで実行されている複数のスレッドがデータをキャッシュし、命令を並べ替えることができるし、またそうすることです。

それがnotvolatileで、CPU Aが値を増加させると、しばらくするとCPU Bは実際にはその増加された値を認識できず、これが問題を引き起こす可能性があります。

それがvolatileであるなら、これはちょうど2つのCPUが同時に同じデータを見ることを保証します。それは彼らがあなたが避けようとしている問題である彼らの読み書き操作をインターリーブすることから彼らを全く止めません。

セカンドベスト:

lock(this.locker) this.counter++;

これは安全です(あなたがthis.counterにアクセスする他の場所でlockを忘れないでください)。他のスレッドがlockerで保護されている他のコードを実行するのを防ぎます。ロックを使用すると、上記のようにマルチCPUの並べ替えの問題を防ぐことができます。これは素晴らしいことです。

問題は、ロックが遅いこと、そして実際には関係のない他の場所でlockerを再利用すると、理由なく他のスレッドをブロックしてしまうことです。

ベスト

Interlocked.Increment(ref this.counter);

これは安全です。中断することができない「1回のヒット」で読み取り、増分、および書き込みを効果的に実行できるためです。このため、他のコードには影響しません。また、他の場所でロックすることを忘れないでください。それはまた非常に速いです(MSDNが言うように、現代のCPUでは、これは文字通り単一のCPU命令です)。

しかし、それが他のCPUを回避して物事の順序を変えたのか、それとも揮発性と増分を組み合わせる必要があるのか​​、私は完全にはわかりません。

連動注:

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

脚注:実際には何が揮発性が良いのか。

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

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

解決策はロックすることですが、この状況でvolatileを使用することもできます。これにより、スレッドAが書き込んだ最新のものがスレッドBに常に表示されるようになります。ただし、この論理のみを読んだことのない作家と書いたことのない読者がいる場合はあなたが書いているものがアトミック値であれば。単一の読み取り - 変更 - 書き込みを実行したらすぐに、インターロック操作に進むか、またはLockを使用する必要があります。

811
Orion Edwards

EDIT:コメントで述べたように、最近は単一変数の場合にInterlockedを使うのが嬉しいです。明らかにはい。それがもっと複​​雑になっても、ロックに戻ります。

volatileを使用しても、インクリメントする必要がある場合は役に立ちません - 読み取りと書き込みが別々の命令であるためです。読み終わってから書き戻す前に、別のスレッドが値を変更する可能性があります。

個人的には、私はほとんどいつもロックしています - ボラティリティやInterlocked.Incrementよりも明らかにrightのようにするのが簡単です。私の知る限りでは、ロックフリーのマルチスレッディングは本物のスレッディングのエキスパートのためのものですが、私はその一人ではありません。 Joe Duffyと彼のチームが私が構築するものほどロックすることなく物事を並列化するNiceライブラリを構築するならば、それは素晴らしいことです、そして私はそれをハートビートで使うつもりです - 複雑にしないでおく。

135
Jon Skeet

"volatile"はInterlocked.Incrementに代わるものではありません。変数がキャッシュされずに直接使用されるようにするだけです。

変数を増やすには、実際には3つの操作が必要です。

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

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

42
Michael Damatov

あなたが探しているのは、ロックまたはインターロック増分のどちらかです。

現在のコードパスでコンパイラがメモリからの読み込みを最適化できる場合でも、Volatileは間違いなくあなたが望むものではありません - 単に変数を常に変化するものとして扱うようにコンパイラに指示します。

例えば.

while (m_Var)
{ }

別のスレッドでm_Varがfalseに設定されていてもvolatileとして宣言されていない場合、コンパイラはCPUレジスタに対してチェックすることで、無限ループにすることができます(ただし、常に実行されるわけではありません)。 m_Varのメモリ位置に別のreadを発行する代わりに、m_Varが最初からフェッチされたものです(これはキャッシュされる可能性があります - わからないし、気にする必要はありません。これがx86/x64のキャッシュ一貫性のポイントです)。命令の並べ替えについて述べた他の人による以前の記事はすべて、単にx86/x64アーキテクチャーを理解していないことを示しています。以前の投稿で「並べ替えを妨げる」と言っていたように、volatileは読み書きの障壁を発行しませんしません。実際、MESIプロトコルのおかげで、実際の結果が物理メモリにリタイアされたか、単にローカルCPUのキャッシュにあるかにかかわらず、読み取った結果がCPU間で常に同じになることが保証されます。この詳細については詳しく説明しませんが、これがうまくいかない場合、Intel/AMDがプロセッサのリコールを発行する可能性があることを安心してください。これはまた、順不同の実行などを気にする必要がないことを意味します。結果は常に順番に引退することが保証されています。

他のプロセッサが変更できないようにするには、キャッシュライン全体の排他的所有権(lock xadd)を保持しながら、プロセッサは指定されたアドレスから値を取得し、それから値を書き込みます。その価値.

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

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

詳細については、「マルチスレッドアプリケーションでの低ロック技術の影響について」を参照してください - http://msdn.Microsoft.com/ja-jp/magazine/cc163715.aspx

pSこの非常に遅い回答を促したのは何ですか?すべての返事は(特に返事としてマークされているもの)彼らの説明で非常に露骨に間違っていました私はちょうどこれを読んでいる誰かのためにそれを片付けなければなりませんでした。 肩をすくめる

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

41
Zach Saw

連動機能はロックしません。これはアトミックです。つまり、インクリメント中にコンテキストが切り替わる可能性がなくても完了できるということです。そのため、デッドロックや待機の可能性はありません。

私はあなたがいつもそれをロックして増加することを好むべきであると言うでしょう。

あるスレッドで書き込みをして別のスレッドで読み込む必要がある場合や、オプティマイザが変数の操作を並べ替えないようにする場合(オプティマイザが認識していない別のスレッドで問題が発生しているため)は、volatileが便利です。それはあなたがどのように増加するかに対する直交的な選択です。

あなたがロックフリーコードについてもっと知りたいなら、これは本当に良い記事です、そして、それを書くことへのアプローチへの正しい方法

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

15
Lou Franco

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

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

揮発性はそれ自体では正しくありません。変更された値を検索して書き込もうとしたスレッドは、同じことを行っている別のスレッドと競合する可能性があります。

11
Rob Walker
8
zihotki

理論が実際にどのように機能するかを確認するためにいくつかテストを行いました。 kennethxu.blogspot.com/2009/05/interlocked-vs-monitor-performance.html 。私のテストはCompareExchnageに焦点を当てていましたが、Incrementの結果は似ています。マルチCPU環境では、インターロックは高速である必要はありません。これは、2歳の16 CPUサーバーにおけるIncrementのテスト結果です。テストには増加後の安全な読み取りも含まれますが、これは現実の世界では一般的なことです。

D:\>InterlockVsMonitor.exe 16
Using 16 threads:
          InterlockAtomic.RunIncrement         (ns):   8355 Average,   8302 Minimal,   8409 Maxmial
    MonitorVolatileAtomic.RunIncrement         (ns):   7077 Average,   6843 Minimal,   7243 Maxmial

D:\>InterlockVsMonitor.exe 4
Using 4 threads:
          InterlockAtomic.RunIncrement         (ns):   4319 Average,   4319 Minimal,   4321 Maxmial
    MonitorVolatileAtomic.RunIncrement         (ns):    933 Average,    802 Minimal,   1018 Maxmial
7
Kenneth Xu

volatileInterlocked、およびlockの違いを他の回答で言及したことに追加したいと思います。

volatileキーワードはこれらのタイプのフィールドに適用できます

  • 参照タイプ。
  • ポインター型(安全でないコンテキスト内)。ポインター自体は揮発性である場合がありますが、ポインターが指すオブジェクトは揮発性ではないことに注意してください。つまり、「ポインター」を「揮発性」として宣言することはできません。
  • sbytebyteshortushortintuintcharfloatboolなどの単純型。
  • 次の基本型のいずれかを持つ列挙型:bytesbyteshort、ushort、int、またはuint
  • 参照型として知られているジェネリック型パラメーター。
  • IntPtrおよびUIntPtr

doublelongを含む他のタイプは、これらのタイプのフィールドへの読み取りと書き込みがアトミックであることを保証できないため、「volatile」とマークできません。これらのタイプのフィールドへのマルチスレッドアクセスを保護するには、Interlockedクラスメンバを使用するか、lockステートメントを使用してアクセスを保護します。

2
Vadim S.