この複雑な質問を分解して明確にしようと思います。
「紹介を読む」とは何ですか?
「はじめに読む」は、コードが次のように最適化されたものです。
public static Foo foo; // I can be changed on another thread!
void DoBar() {
Foo fooLocal = foo;
if (fooLocal != null) fooLocal.Bar();
}
ローカル変数を削除することで最適化されます。コンパイラーは、スレッドが1つしかない場合、foo
とfooLocal
は同じであると推論できます。コンパイラは、マルチスレッドシナリオで表示されるようになった場合でも、シングルスレッドでは表示されない最適化を行うことを明示的に許可されています。したがって、コンパイラはこれを次のように書き換えることができます。
void DoBar() {
if (foo != null) foo.Bar();
}
そして今、競合状態があります。foo
チェック後にnull以外からnullに変わった場合はfoo
、2回目に読み取られ、2回目にnullになる可能性があり、クラッシュする可能性があります。クラッシュダンプを診断する人の観点からすると、これは完全に不思議です。
これは実際に起こり得ますか?
あなたがリンクした記事が呼びかけたように:
x86-x64上の.NETFramework4.5でこのコードサンプルを使用して、NullReferenceExceptionを再現することはできないことに注意してください。.NET Framework 4.5でリードイントロダクションを再現することは非常に困難ですが、それでも特定の特別な状況で発生します。
x86 / x64チップには「強力な」メモリモデルがあり、jitコンパイラはこの分野では積極的ではありません。彼らはこの最適化を行いません。
ARMチップなどの弱いメモリモデルプロセッサでコードを実行している場合は、すべての賭けが無効になります。
「コンパイラ」と言うとき、どのコンパイラを意味しますか?
私はjitコンパイラを意味します。C#コンパイラは、この方法で読み取りを導入することはありません。(許可されていますが、実際には許可されていません。)
メモリバリアなしでスレッド間でメモリを共有することは悪い習慣ではありませんか?
はい。の値はfoo
すでにプロセッサキャッシュにキャッシュされた古い値である可能性があるため、ここでメモリバリアを導入するために何かを行う必要があります。メモリバリアを導入するための私の好みは、ロックを使用することです。フィールドを作成するvolatile
か、を使用するVolatileRead
か、またはいずれかのInterlocked
方法を使用することもできます。それらはすべてメモリバリアをもたらします。(volatile
参考までに「ハーフフェンス」のみを紹介します。)
メモリバリアがあるからといって、必ずしも読み取り導入の最適化が実行されないことを意味するわけではありません。ただし、メモリバリアを含むコードに影響を与える最適化の追求については、ジッターはそれほど積極的ではありません。
このパターンに他の危険はありますか?
もちろん!読まれた紹介がないと仮定しましょう。まだ競合状態があります。foo
チェック後に別のスレッドがnullに設定され、消費するグローバル状態も変更しBar
た場合はどうなりますか?これで2つのスレッドができました。1つfoo
はnullではなくグローバル状態はへの呼び出しに問題がないと考えており、もう1Bar
つは反対のスレッドを信じて実行していますBar
。これは災害のレシピです。
では、ここでのベストプラクティスは何ですか?
まず、スレッド間でメモリを共有しないでください。プログラムのメインライン内に2つの制御スレッドがあるというこの全体的な考えは、そもそもクレイジーです。そもそもそうあるべきではなかった。スレッドを軽量プロセスとして使用します。プログラムのメインラインのメモリとまったく相互作用しない独立したタスクを実行するように与え、それらを使用して計算量の多い作業を実行します。
次に、スレッド間でメモリを共有する場合は、ロックを使用してそのメモリへのアクセスをシリアル化します。ロックが競合しない場合は安価であり、競合がある場合は、その問題を修正します。ローロックおよびノーロックのソリューションは、正しく理解するのが難しいことで有名です。
第3に、スレッド間でメモリを共有する場合、その共有メモリを含む呼び出し元のすべてのメソッドは、競合状態に直面しても堅牢であるか、競合を排除する必要があります。それは負担が大きいので、そもそもそこに行くべきではありません。
私のポイントは、イントロダクションを読むのは怖いですが、率直に言って、スレッド間でメモリを簡単に共有するコードを書いている場合は、心配する必要はほとんどありません。最初に心配することは千と他にあります。