web-dev-qa-db-ja.com

Interlocked.Increment'ed intフィールドを正しく読み取る方法は?

不揮発性のintフィールドと、それを_Interlocked.Increment_ sするスレッドがあるとします。別のスレッドがこれを直接安全に読み取ることができますか、または読み取りもインターロックする必要がありますか?

結局のところ、フィールドは揮発性ではないため、現在の値が表示されることを保証するために、インターロックされた読み取りを使用する必要があると以前考えていました。私はそれを達成するためにInterlocked.CompareExchange(int, 0, 0)を使用しています。

しかし、私は偶然に遭遇しました この答え これは、実際のプレーンな読み取りでは常に現在のバージョンの_Interlocked.Increment_ ed値が表示されることを示唆しており、int読み取りはすでにアトミックなので、実行する必要はありませんなにか特別なもの。私は MicrosoftがInterlocked.Read(ref int)のリクエストを拒否するリクエスト も見つけました。これは完全に冗長であることをさらに示唆しています。

それで、intがなくても、そのようなInterlockedフィールドの最新の値を本当に安全に読み取ることができますか?

57
Roman Starkov

他のスレッドが最新の値を読み取ることを保証したい場合は、 Thread.VolatileRead() を使用する必要があります。 (*)

読み取り操作自体はアトミックであるため、問題は発生しませんが、揮発性読み取りがなければ、キャッシュから古い値が取得されるか、コンパイラーがコードを最適化して、読み取り操作を完全に排除できます。コンパイラーの観点からは、コードがシングルスレッド環境で機能することで十分です。揮発性操作とメモリバリアは、コードを最適化して並べ替えるコンパイラの機能を制限するために使用されます。

コンパイラ、JITコンパイラ、CPUなど、コードを変更できる参加者がいくつかいます。それらのどれがあなたのコードが壊れていることを示しているかは問題ではありません。唯一の重要なことは、。NETメモリモデルであり、すべての参加者が従う必要のあるルールを指定します。

(*)Thread.VolatileRead()は実際には最新の値を取得しません。値を読み取り、読み取り後にメモリバリアを追加します。最初の揮発性の読み取りはキャッシュされた値を取得しますが、最初の揮発性の読み取りのメモリバリアが必要に応じてキャッシュの更新を強制したため、2番目の値は更新された値を取得します。実際には、この詳細はコードを書くときにほとんど重要ではありません。

19
mgronber

少しメタの問題ですが、Interlocked.CompareExchange(ref value, 0, 0)を使用することの優れた点(読み取りに使用すると理解が難しいという明らかな欠点を無視)は、intまたはlongに関係なく機能することです。 intの読み取りは常にアトミックですが、アーキテクチャによってはlongの読み取りはそうでないか、そうでない場合があります。残念ながら、Interlocked.Read(ref value)は、valuelong型である場合にのみ機能します。

intフィールドで開始し、Interlocked.Read()を使用できなくなった場合を考えてみます。そうすれば、とにかくアトミックなので、代わりに値を直接読み取ることになります。ただし、開発の後半で、あなたまたは誰かがlongが必要であると判断しました-コンパイラーは警告しませんが、今では微妙なバグがある可能性があります:読み取りアクセスはアトミックであることが保証されていません。私はここでInterlocked.CompareExchange()を使用するのが最善の選択肢であるとわかりました。基盤となるプロセッサの命令によっては遅くなる可能性がありますが、長期的には安全です。 Thread.VolatileRead()の内部についてはよくわかりません。さらに多くのシグネチャを提供するため、このユースケースに関しては「良い」かもしれません。

ループまたはタイトなメソッド呼び出し内で値を直接(つまり、上記のメカニズムを使用せずに)読み取ろうとはしません。書き込みが揮発性であるかメモリバリアであるかどうかにかかわらず、コンパイラにフィールドの値は、実際には2つのreadsの間で変更できます。したがって、フィールドはvolatileであるか、指定された構成のいずれかを使用する必要があります。

私の2セント。

13
sunside

32ビット整数をアトミックに読み取るための特別な命令は必要ないことは正しいですが、つまり、「全体」の値が取得されます(つまり、の一部が取得されません)書き込みと別の一部)。一度読み取った値が変更されないという保証はありません。

この時点で、アクセスを制御するために他の同期方法を使用する必要があるかどうか、たとえば、この値を使用して配列からメンバーを読み取るかどうかなどを決定する必要があります。


簡単に言うと、atomicityは、操作が完全かつ不可分に行われることを保証します。 Aステップを含むいくつかの操作Nがある場合、Aの直後に操作を実行すると、すべてのNステップが独立して発生したことが保証されます。同時操作から。

アトミック操作Aを実行する2つのスレッドがあった場合、completeの結果のみが表示されますスレッド。スレッドを調整する場合は、アトミック操作を使用して必要な同期を作成できます。ただし、アトミック操作自体は、より高いレベルの同期を提供しません。 Interlockedファミリーのメソッドは、いくつかの基本的なアトミック操作を提供するために使用可能になります。

Synchronizationは、atomic操作を中心に構築された、より広範な種類の同時実行制御です。ほとんどのプロセッサには、すべてのキャッシュラインがフラッシュされ、メモリのconsistentビューがあることを確認できるメモリバリアが含まれています。揮発性読み取りは、特定のメモリ位置への一貫したアクセスを保証する方法です。

問題にすぐには当てはまりませんが、データベースに関してACID(原子性、一貫性、分離性、耐久性)を読むと、用語の理解に役立つ場合があります。

8
user7116