(これは繰り返しです: Interlocked.Increment'ed intフィールドを正しく読み取る方法は? しかし、回答とコメントを読んだ後でも、正しい答えがわかりません。)
私が所有していないコードがいくつかあり、いくつかの異なるスレッドでintカウンター(numberOfUpdates)をインクリメントするロックを使用するように変更できません。すべての通話で使用:
Interlocked.Increment(ref numberOfUpdates);
コードでnumberOfUpdatesを読みたいのですが。これはintなので、破れないことはわかっています。しかし、私が可能な限り最新の価値を確実に得るための最良の方法は何ですか?私のオプションは次のようです:
int localNumberOfUpdates = Interlocked.CompareExchange(ref numberOfUpdates, 0, 0);
または
int localNumberOfUpdates = Thread.VolatileRead(numberOfUpdates);
どちらも機能しますか(最適化、並べ替え、キャッシングなどに関係なく、可能な限り最新の値を提供するという意味で)?どちらが優先されますか?より良い3番目のオプションはありますか?
インターロックを使用して共有データをインクリメントする場合は、その共有データにアクセスするすべての場所でインターロックを使用する必要があると私は確信しています。同様に、お気に入りの同期プリミティブをここに挿入して共有データをインクリメントする場合は、お気に入りを挿入する必要がありますここで同期プリミティブは、その共有データにアクセスするすべての場所にあります。
int localNumberOfUpdates = Interlocked.CompareExchange(ref numberOfUpdates, 0, 0);
あなたが探しているものを正確に提供します。他の人が言ったように、インターロックされた操作はアトミックです。したがって、Interlocked.CompareExchangeは常に最新の値を返します。私はこれを、カウンタのような単純な共有データにアクセスするために常に使用しています。
Thread.VolatileReadについてはあまり詳しくありませんが、最新の値も返されると思います。一貫性を保つためだけに、私はインターロックされたメソッドを使い続けます。
追加情報:
Thread.VolatileRead()を避けたい理由について、Jon Skeetの回答を確認することをお勧めします。 Thread.VolatileRead Implementation
Eric Lippertは、ボラティリティとC#メモリモデルによって行われた保証について、彼のブログ http://blogs.msdn.com/b/ericlippert/archive/2011/06/16/atomicity-volatility-and-immutabilityで説明しています。 -are-different-part-three.aspx 。馬の口から直接:「Interlockedオペレーションの最も簡単な使用法を除いて、ローロックコードを記述しようとはしません。 "volatile"の使用法は、実際の専門家に任せます。」
そして、値は常に少なくとも数ns古くなっているというハンスのポイントに同意しますが、それが受け入れられないユースケースがある場合、C#や非現実的なガベージコレクション言語にはおそらくあまり適していません。時間OS。 Joe Duffyは、ここでインターロックされたメソッドの適時性に関する良い記事を持っています: http://joeduffyblog.com/2008/06/13/volatile-reads-and-writes-and-timeliness/
Thread.VolatileRead(numberOfUpdates)
は必要なものです。 numberOfUpdates
はInt32
であるため、デフォルトですでにアトミック性があり、Thread.VolatileRead
はボラティリティが確実に処理されるようにします。
numberOfUpdates
がvolatile int numberOfUpdates;
として定義されている場合、そのすべての読み取りはすでに揮発性読み取りであるため、これを行う必要はありません。
Interlocked.CompareExchange
がより適切であるかどうかについて混乱があるようです。ドキュメントからの次の2つの抜粋を検討してください。
Thread.VolatileRead
ドキュメントから:
フィールドの値を読み取ります。値は、プロセッサの数やプロセッサキャッシュの状態に関係なく、コンピュータ内の任意のプロセッサによって書き込まれた最新のものです。
Interlocked.CompareExchange
ドキュメントから:
2つの32ビット符号付き整数が等しいかどうかを比較し、等しい場合は、値の1つを置き換えます。
これらのメソッドの動作については、Thread.VolatileRead
の方が明らかに適切です。 numberOfUpdates
を別の値と比較したくないし、その値を置き換えたくない。あなたはその値を読みたいです。
ラッセは彼のコメントで良い点を述べています:あなたは単純なロックを使う方が良いかもしれません。他のコードがnumberOfUpdates
を更新したい場合、次のようなことを行います。
lock (state)
{
state.numberOfUpdates++;
}
読みたいときは、次のようにします。
int value;
lock (state)
{
value = state.numberOfUpdates;
}
これにより、よりあいまいで比較的低レベルのマルチスレッドプリミティブを掘り下げることなく、原子性とボラティリティの要件を確保できます。
どちらも機能しますか(最適化、並べ替え、キャッシングなどに関係なく、可能な限り最新の値を提供するという意味で)?
いいえ、取得する値はalways古くなっています。値がどれほど古くなっているかは、まったく予測できません。ほとんどの場合、値にどれだけ速く作用するかに応じて、数ナノ秒で失効します。しかし、妥当な上限はありません。
値をどのように処理する場合でも、これを考慮する必要があります。言うまでもなく、それは非常に難しいことです。実用的な例はなかなか見つかりません。 .NET Frameworkは、非常に大きな戦いで傷ついたコードの塊です。 参照ソース からのVolatileReadの使用への相互参照を確認できます。ヒット数:0。
まあ、ハンス・パッサントが言ったように、あなたが読むどんな価値も常にいくぶん古くなります。 other共有値が、いくつかの共有値を読み取るコードの途中でメモリフェンスを使用して読み取ったものと一致するという保証のみを制御できますロックなし(つまり、「古さ」と同程度)
フェンスは、一部のコンパイラの最適化を無効にし、順序を変更する効果もあるので、異なるプラットフォームのリリースモードでの予期しない動作を防ぎます。
Thread.VolatileReadは、完全なメモリフェンスを生成します。これにより、intの読み取り(それを読み取っているメソッド内)の周囲で読み取りまたは書き込みを並べ替えることができなくなります。明らかに単一の共有された値のみを読み取っている場合(そして他の共有された値を読み取っていない場合、それらの両方の順序と一貫性は重要です)、それは必要ではないと思われるかもしれません...
しかし、コンパイラーまたはCPUによる最適化を無効にして、必要以上に「古くなった」読み取りを取得しないようにするには、いずれにせよそれが必要になると思います。
ダミーのInterlocked.CompareExchangeは、Thread.VolatileRead(完全なフェンスと最適化を無効にする動作)と同じことを行います。
CancellationTokenSourceによって使用されるフレームワークで実行されるパターンがあります http://referencesource.Microsoft.com/#mscorlib/system/threading/CancellationTokenSource.cs#64
//m_state uses the pattern "volatile int32 reads, with cmpxch writes" which is safe for updates and cannot suffer torn reads.
private volatile int m_state;
public bool IsCancellationRequested
{
get { return m_state >= NOTIFYING; }
}
// ....
if (Interlocked.CompareExchange(ref m_state, NOTIFYING, NOT_CANCELED) == NOT_CANCELED) {
}
// ....
Volatileキーワードは、「半分」のフェンスを放出する効果があります。 (つまり、読み取り/書き込みが読み取り前に移動されるのをブロックし、読み取り/書き込みが書き込み後に移動されないようにブロックします)。