web-dev-qa-db-ja.com

他のスレッドでInterlockedによって更新されたintを読み取る

(これは繰り返しです: 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番目のオプションはありますか?

39
Michael Covelli

インターロックを使用して共有データをインクリメントする場合は、その共有データにアクセスするすべての場所でインターロックを使用する必要があると私は確信しています。同様に、お気に入りの同期プリミティブをここに挿入して共有データをインクリメントする場合は、お気に入りを挿入する必要がありますここで同期プリミティブは、その共有データにアクセスするすべての場所にあります。

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/

38
Pat Hensel

Thread.VolatileRead(numberOfUpdates)は必要なものです。 numberOfUpdatesInt32であるため、デフォルトですでにアトミック性があり、Thread.VolatileReadはボラティリティが確実に処理されるようにします。

numberOfUpdatesvolatile 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;
}

これにより、よりあいまいで比較的低レベルのマルチスレッドプリミティブを掘り下げることなく、原子性とボラティリティの要件を確保できます。

6
Timothy Shields

どちらも機能しますか(最適化、並べ替え、キャッシングなどに関係なく、可能な限り最新の値を提供するという意味で)?

いいえ、取得する値はalways古くなっています。値がどれほど古くなっているかは、まったく予測できません。ほとんどの場合、値にどれだけ速く作用するかに応じて、数ナノ秒で失効します。しかし、妥当な上限はありません。

  • 別のスレッドをコアにコンテキストスイッチすると、スレッドがプロセッサを失う可能性があります。一般的な遅延は約45ミリ秒で、上限はありません。これはしないは、プロセス内の別のスレッドもスイッチアウトされることを意味し、モーターを維持し続け、値を変更し続けることができます。
  • ユーザーモードコードと同様に、コードもページフォールトの影響を受けます。プロセッサが他のプロセスにRAM=を必要とする場合に発生します。アクティブなコードをページアウトすることができ、ページアウトする高負荷のマシン上。マウスドライバーのコードなどで時々発生するが、マウスカーソルがフリーズしたままになる。
  • 管理対象スレッドは、ほぼランダムなガベージコレクションの一時停止の影響を受けます。値を変更している別のスレッドも一時停止される可能性が高いため、問題は少なくなります。

値をどのように処理する場合でも、これを考慮する必要があります。言うまでもなく、それは非常に難しいことです。実用的な例はなかなか見つかりません。 .NET Frameworkは、非常に大きな戦いで傷ついたコードの塊です。 参照ソース からのVolatileReadの使用への相互参照を確認できます。ヒット数:0。

3
Hans Passant

まあ、ハンス・パッサントが言ったように、あなたが読むどんな価値も常にいくぶん古くなります。 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キーワードは、「半分」のフェンスを放出する効果があります。 (つまり、読み取り/書き込みが読み取り前に移動されるのをブロックし、読み取り/書き込みが書き込み後に移動されないようにブロックします)。

2
Raif Atef