C# では、これはスレッド セーフな方法でイベントを呼び出すための標準コードです。
var handler = SomethingHappened;
if(handler != null)
handler(this, e);
場合によっては別のスレッドで、コンパイラによって生成された add メソッドが使用Delegate.Combine
して新しいマルチキャスト デリゲート インスタンスを作成し、それをコンパイラによって生成されたフィールドに設定します (インターロックされた比較交換を使用)。
(注: この質問の目的のために、イベント サブスクライバーで実行されるコードは気にしません。削除に直面してもスレッドセーフで堅牢であると仮定します。)
私自身のコードでは、次の行に沿って同様のことをしたいと考えています。
var localFoo = this.memberFoo;
if(localFoo != null)
localFoo.Bar(localFoo.baz);
this.memberFoo
別のスレッドで設定できる場所。(これは 1 つのスレッドなので、インターロックする必要はないと思いますが、ここに副作用があるのではないでしょうか?)
Foo
(そして、明らかに、それがこのスレッドで使用されている間は積極的に変更しないように「十分に不変」であると仮定します。)
これで、これがスレッドセーフであるという明白な理由がわかりました。参照フィールドからの読み取りはアトミックです。ローカルにコピーすることで、2 つの異なる値を取得しないことが保証されます。(どうやら.NET 2.0 からのみ保証されているようですが、正常な .NET 実装では安全だと思いますか?)
しかし、私が理解していないのは、参照されているオブジェクトインスタンスによって占有されているメモリはどうですか? 特にキャッシュの一貫性に関しては? 「ライター」スレッドが 1 つの CPU でこれを行う場合:
thing.memberFoo = new Foo(1234);
new が割り当てられるメモリが、初期化されていない値で、「リーダー」が実行されている CPU のキャッシュにないことを保証するものは何Foo
ですか? localFoo.baz
(上記)がガベージを読み取らないことを保証するものは何ですか? (そして、これはプラットフォーム間でどの程度保証されていますか?Monoで?ARMで?)
新しく作成された foo がたまたまプールから取得された場合はどうなるでしょうか。
thing.memberFoo = FooPool.Get().Reset(1234);
これは、メモリの観点から見ると、新しい割り当てと同じように見えますが、.NET アロケータが最初のケースを機能させる魔法を使っているのではないでしょうか?
これを尋ねる際の私の考えは、メモリバリアが必要になるということです-読み取りが依存していることを考えると、メモリアクセスを移動できないほどではありませんが、キャッシュの無効化をフラッシュするためのCPUへの信号として.
これの私の情報源はウィキペディアです。
(おそらく、ライタースレッドのインターロック比較交換がリーダーのキャッシュを無効にするのではないかと推測するかもしれません。それとも、すべての読み取りが無効化の原因になるのでしょうか?それとも、ポインターの逆参照が無効化の原因になるのでしょうか?私は特に、プラットフォーム固有のこれらのことがどのように聞こえるかを懸念しています。)
更新:質問がCPUキャッシュの無効化と.NETが提供する保証(およびそれらの保証がCPUアーキテクチャにどのように依存するか)に関するものであることをより明確にするために:
- フィールド
Q
(メモリの場所) に格納された参照があるとします。 - CPU A (ライター) では、メモリ位置 でオブジェクトを初期化し、へ
R
の参照を書き込みますR
Q
- CPU B (リーダー) では、フィールドを逆参照
Q
し、メモリの場所を取得します。R
- 次に、CPU Bで、値を読み取ります。
R
GC がどの時点でも実行されていないと仮定します。他に興味深いことは何も起こりません。
質問: Aが初期化中にそれを変更する前から、 Bのキャッシュに存在することを妨げているものは何ですか? R
Bが最初にどこにあるかを知るために新しいバージョンを取得しているにもかかわらず、 Bがそれから読み取ったときに古い値を取得します? R
Q
R
(別の言い方: への変更がR
CPU Bに見える時点またはその前に、変更をCPU BQ
に見えるようにするもの。)
(そして、これは で割り当てられたメモリ、または任意のメモリにのみ適用されますnew
か?)+
注:ここに自己回答を投稿しました。