52

(これは、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 のオプションはありますか?

4

5 に答える 5

49

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

int localNumberOfUpdates = Interlocked.CompareExchange(ref numberOfUpdates, 0, 0);

あなたが探しているものを正確に提供します。他の人が言ったように、連動操作はアトミックです。したがって、Interlocked.CompareExchange は常に最新の値を返します。カウンターなどの単純な共有データにアクセスするために、これを常に使用しています。

私は Thread.VolatileRead に精通していませんが、最新の値も返すと思います。一貫性を保つためだけに、連動した方法に固執します。


追加情報:

Thread.VolatileRead(): Thread.VolatileRead Implementationを敬遠する理由については、Jon Skeet の回答を参照することをお勧めします。

Eric Lippert は、彼のブログ ( http://blogs.msdn.com/b/ericlippert/archive/2011/06/16/atomicity-volatility-and-immutability-are-different ) で、揮発性と C# メモリ モデルによる保証について説明しています。 -part-three.aspx . 馬の口からまっすぐに: 「インターロック操作の最も些細な使用法を除いて、ローロック コードを記述しようとはしません。「揮発性」の使用法は真の専門家に任せます。」

そして、値は常に少なくとも数nsは古くなるというHansの指摘に同意しますが、それが受け入れられないユースケースがある場合、C#や非現実的な言語などのガベージコレクション言語にはおそらく適していません。時間OS。Joe Duffy は、インターロックされたメソッドの適時性に関する優れた記事をここに掲載しています: http://joeduffyblog.com/2008/06/13/volatile-reads-and-writes-and-timeliness/

于 2014-07-22T16:50:16.753 に答える
6

Thread.VolatileRead(numberOfUpdates)あなたが望むものです。numberOfUpdatesであるInt32ため、デフォルトですでに原子性があり、Thread.VolatileRead揮発性が確実に処理されます。

numberOfUpdatesが定義されている場合volatile int numberOfUpdates;、これを行う必要はありません。これは、すべての読み取りが既に揮発性読み取りになっているためです。


Interlocked.CompareExchangeがより適切かどうかについては混乱があるようです。ドキュメントからの次の 2 つの抜粋を検討してください。

Thread.VolatileReadドキュメントから:

フィールドの値を読み取ります。この値は、プロセッサの数やプロセッサ キャッシュの状態に関係なく、コンピューター内の任意のプロセッサによって書き込まれた最新の値です。

Interlocked.CompareExchangeドキュメントから:

2 つの 32 ビット符号付き整数が等しいかどうかを比較し、等しい場合は値の 1 つを置き換えます。

これらのメソッドの宣言された動作に関しては、Thread.VolatileRead明らかにより適切です。別の値と比較numberOfUpdatesしたり、その値を置き換えたりしたくない場合。その値を読み取りたい。


Lasse は彼のコメントで良い点を述べています。単純なロックを使用する方が良いかもしれません。他のコードが更新numberOfUpdatesしたい場合、次のようなことを行います。

lock (state)
{
    state.numberOfUpdates++;
}

読みたいときは、次のようにします。

int value;
lock (state)
{
    value = state.numberOfUpdates;
}

これにより、より曖昧で比較的低レベルのマルチスレッドプリミティブを掘り下げることなく、原子性と揮発性の要件を確実に満たすことができます。

于 2014-07-17T16:07:16.643 に答える
3

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

いいえ、取得する値は常に古くなっています。値がどの程度古くなるかは、まったく予測できません。ほとんどの場合、値に基づいて行動する速度に応じて、ギブまたはテイクで数ナノ秒古くなります。しかし、合理的な上限はありません。

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

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

于 2014-07-22T17:37:38.060 に答える