を参照してください上のコードを実行しますメモリモデル(少なくとも2.0以降) は、ロック が完全メモリフェンスを提供するので、そのインスタンスをvolatileとして宣言する必要はありません。そうでないか、私は 誤っていましたか?
これは必須です。理由は、lock
以外のinstance
にアクセスしているためです。 volatile
を省略し、すでにこのような初期化の問題を修正しているものとします。 C#コンパイラ、JITコンパイラ、またはハードウェアがtemp
変数を離れて最適化し、Init
が実行される前に割り当てられますしinstance
変数の原因となる命令シーケンスを発することができ、いくつかのレベルでは
public class Foo
{
private static Foo instance;
private static readonly object padlock = new object();
public static Foo GetValue()
{
if (instance == null)
{
lock (padlock)
{
if (instance == null)
{
var temp = new Foo();
temp.Init();
instance = temp;
}
}
}
return instance;
}
private void Init() { /* ... */ }
}
。実際には、コンストラクタが実行される前でもinstance
を割り当てることができます。 Init
メソッドを使用すると、問題を見つけるのがはるかに簡単になりますが、問題はコンストラクタにも残ります。
これは、ロック内で命令を自由に並べ替えることができるため、有効な最適化です。 lock
はメモリバリアを発行しますが、Monitor.Enter
とMonitor.Exit
コールでのみ発生します。
ここで、volatile
を省略すると、ほとんどのハードウェア実装とCLI実装の組み合わせでコードが機能するようになります。その理由は、x86ハードウェアのメモリモデルがより緊密で、MicrosoftのCLRの実装もかなり厳しいためです。ただし、この件に関するECMA仕様は比較的緩やかです。つまり、CLIの別の実装では、現在Microsoftが無視する最適化を自由に行うことができます。ほとんどの人が集中する傾向のあるハードウェアではなく、CLIジッタである弱いモデルをコーディングする必要があります。このため、まだvolatile
が必要です。
複数のスレッドに関して読取り/書込み再順序付けは観測できません。単一のスレッドでは、側面 のエフェクトは一貫した順序であり、その場所のロック は、他のスレッドが何かを見逃してしまうのを防ぎます。 こちらもオフベースですか?
はい。命令の並べ替えは、複数のスレッドが同じメモリ位置にアクセスしている場合にのみ開始されます。たとえ最も弱いソフトウェアやハードウェアのメモリモデルであっても、コードがスレッド上で実行されているときに開発者が意図したものから動作を変更するような最適化は許可されません。それ以外の場合、プログラムは正しく実行されません。問題はどのように他のスレッドがそのスレッドで何が起こっているかを観察することです。他のスレッドは、実行中のスレッドの動作とは異なる動作を認識することがあります。しかし、実行スレッドは常に正しい動作を認識します。
いいえ、lock
は、それだけで、他のスレッドが異なる一連のイベントを認識するのを妨げません。理由は、実行中のスレッドが、開発者が意図した順序とは異なる順序でlock
内部の命令を実行している可能性があるからです。メモリバリアが作成されるのは、ロックの入口と出口だけです。したがって、あなたの例では、lock
でこれらの命令をラップしたにもかかわらず、コンストラクタが実行される前でも新しいオブジェクトへの参照をinstance
に割り当てることができます。 volatile
を使用して
は、他の一方で、lock
振る舞う内部コードの共通の知恵にもかかわらず、法の冒頭にinstance
の初期チェックと比較してどのように大きな影響を与えています。多くの人々は、大きな問題はinstance
が揮発性の読み込みなしで失効している可能性があると考えています。そうかもしれないが、より大きな問題は、lock
内に揮発性の書き込みがなければ、別のスレッドがコンストラクタがまだ実行されていないインスタンスを参照しているinstance
を参照する可能性があるということです。揮発性書き込みは、書き込み後にコンストラクタコードをinstance
に移動させないため、この問題を解決します。それがvolatile
が依然として必要な大きな理由です。
.NET 2.0メモリモデルについては正しいです。あなたは 'volatile'を必要としません。なぜなら、' lock'は実際に完全なフェンスを行います。しかし、Chibacityが指摘しているように、スレッドセーフについて言えば、競合状態を見落とすことは非常に簡単です。 – Steven