web-dev-qa-db-ja.com

参照の割り当てはアトミックなので、Interlocked.Exchange(ref Object、Object)が必要なのはなぜですか?

マルチスレッドasmx Webサービスでは、List<T>およびvolatileとしてマークされたDictionary<T>で構成される独自のタイプSystemDataのクラスフィールド_allDataがありました。システムデータ(_allData)は時々更新され、newDataと呼ばれる別のオブジェクトを作成し、そのデータ構造に新しいデータを入れて更新します。それが終わったら、私はただ割り当てる

private static volatile SystemData _allData

public static bool LoadAllSystemData()
{
    SystemData newData = new SystemData();
    /* fill newData with up-to-date data*/
     ...
    _allData = newData.
} 

割り当てはアトミックであり、古いデータへの参照を持つスレッドはそれを使用し続け、残りは割り当て直後に新しいシステムデータを持つため、これは機能するはずです。しかし、私の同僚は、volatileキーワードと単純な割り当てを使用する代わりに、InterLocked.Exchangeを使用する必要があると言いました。プラットフォームによっては、参照の割り当てがアトミックであることは保証されないからですさらに:the _allDataフィールドをvolatileとして宣言すると

Interlocked.Exchange<SystemData>(ref _allData, newData); 

「volatileフィールドへの参照はvolatileとして扱われません」という警告を生成します。これについてどう考えますか?

99
char m

ここには多くの質問があります。一度に1つずつ検討します。

参照の割り当てはアトミックなので、なぜInterlocked.Exchange(ref Object、Object)が必要なのですか?

参照の割り当てはアトミックです。 Interlocked.Exchangeは、参照の割り当てのみを行いません。変数の現在の値の読み取りを行い、古い値を隠して、新しい値を変数に割り当てます。すべて原子操作です。

私の同僚は、一部のプラットフォームでは、参照の割り当てがアトミックであることを保証しないと述べました。同僚は正しかったですか?

いいえ。参照の割り当ては、すべての.NETプラットフォームでアトミックであることが保証されています。

私の同僚は間違った前提から推論しています。それは彼らの結論が間違っているということですか?

必ずしも。あなたの同僚は、悪い理由であなたに良いアドバイスを与えているかもしれません。おそらく、Interlocked.Exchangeを使用する必要がある他の理由があります。ロックフリープログラミングはめちゃくちゃ難しく、その分野の専門家が支持する確立されたプラクティスから離れた瞬間に、雑草から逃れ、最悪の種類のレース状態を危険にさらします。私はこの分野の専門家でも、あなたのコードの専門家でもないので、何らかの方法で判断することはできません。

「volatileフィールドへの参照はvolatileとして扱われません」という警告を生成します。これについてどう考えますか?

これが一般的な問題である理由を理解する必要があります。これにより、この特定のケースで警告が重要でない理由を理解できます。

コンパイラがこの警告を出す理由は、フィールドをvolatileとしてマークすると、「このフィールドは複数のスレッドで更新されます-このフィールドの値をキャッシュするコードを生成せず、このフィールドは、プロセッサキャッシュの不整合を介して「時間的に前後に移動する」ことはありません。

(すでにすべてを理解していると思います。volatileの意味と、それがプロセッサキャッシュのセマンティクスに与える影響について詳細に理解していない場合、それがどのように動作するか理解できず、volatileを使用すべきではありません。ロックフリープログラム正しく取得するのは非常に困難です。偶然ではなく、どのように機能するかを理解しているため、プログラムが正しいことを確認してください)

ここで、揮発性フィールドのエイリアスである変数を作成し、そのフィールドにrefを渡すと仮定します。呼び出されたメソッドの内部では、コンパイラは参照に揮発性のセマンティクスが必要であることを知る理由がまったくありません!コンパイラは、揮発性フィールドのルールの実装に失敗したメソッドのコードを元気に生成しますが、変数isは揮発性フィールドです。これはロックフリーロジックを完全に破壊する可能性があります。揮発性フィールドは常にalways揮発性セマンティクスでアクセスされるという仮定です。他の時間ではなく、時々揮発性として扱うことは意味がありません。 always一貫性がなければ、他のアクセスの一貫性を保証できません。

したがって、コンパイラは、これを行うと警告を出します。おそらく、慎重に開発されたロックフリーロジックが完全に台無しになるからです。

もちろん、Interlocked.Exchange isは、volatileフィールドを期待して正しいことをするように記述されています。したがって、警告は誤解を招くものです。私はこれを非常に後悔しています。 Interlocked.Exchangeなどのメソッドの作成者が「refを取得するこのメソッドは変数の揮発性セマンティクスを強制するため、警告を抑制する」という属性をメソッドに設定できるメカニズムを実装する必要があります。おそらくコンパイラの将来のバージョンではそうするでしょう。

168
Eric Lippert

同僚が間違っているか、同僚がC#言語仕様にはない何かを知っているかのどちらかです。

5.5変数参照の原子性

「次のデータ型の読み取りと書き込みはアトミックです:bool、char、byte、sbyte、short、ushort、uint、int、float、および参照型。」

そのため、破損した値を取得するリスクなしに、揮発性参照に書き込むことができます。

もちろん、一度に複数のスレッドがそれを行うリスクを最小限に抑えるために、新しいデータをフェッチするスレッドを決定する方法に注意する必要があります。

10
Guffa

Interlocked.Exchange <T>

指定されたタイプTの変数を指定された値に設定し、アトミック操作として元の値を返します。

変更して元の値を返しますが、変更したいだけなので役に立たず、Guffaが言ったように、すでにアトミックです。

プロファイラーがアプリケーションのボトルネックであることが証明されていない限り、unsingロックを検討する必要があります。コードが正しいことを理解し、証明するのは簡単です。

6
Guillaume

Iterlocked.Exchange()は単なるアトミックではなく、メモリの可視性も考慮します。

次の同期関数は、適切なバリアを使用して、メモリの順序を保証します。

クリティカルセクションに出入りする機能

同期オブジェクトを通知する関数

待機機能

連動機能

同期とマルチプロセッサの問題

これは、原子性に加えて、次のことを保証することを意味します。

  • それを呼び出すスレッドの場合:
    • 命令の並べ替えは行われません(コンパイラ、ランタイム、またはハードウェアによる)。
  • すべてのスレッド:
    • この命令の前に発生するメモリへの読み取りは、この命令による変更を認識しません。
    • この命令の後のすべての読み取りには、この命令によって行われた変更が表示されます。
    • この命令変更後のすべてのメモリへの書き込みは、この命令変更がメインメモリに到達した後に行われます(完了時にこの命令変更をメインメモリにフラッシュし、ハードウェアが自身のオンタイミングをフラッシュしないようにします)。
2
selalerer