これは、私が知る限り、厳密に学術的/理論的な問題です。
私が理解しているように (Vance Morrison、http://msdn.microsoft.com/en-us/magazine/cc163715.aspx、「揮発性メモリ アクセスを作成できません...」)、volatile
C# のキーワード - とりわけ-- 対応するフィールドの追加の読み取りが「コード モーション」によって導入されるのを防ぎます。
を使用しない C# の二重チェック ロックのバリアント ( http://blogs.msdn.com/b/brada/archive/2004/05/12/130935.aspxvolatile
) があります。
public sealed class Singleton
{
private static Singleton value;
private static object syncRoot = new object();
public static Singleton Value
{
get
{
if (value == null)
{
lock (syncRoot)
{
if (value == null)
{
Singleton newVal = new Singleton();
Thread.MemoryBarrier(); // ensures that all is ready to publish
value = newVal;
}
}
}
return value;
}
}
}
ただし、(より強力な .NET 2.0 メモリ モデルではなく) ECMA メモリ モデルのみを想定すると、この亜種は失敗するのではないでしょうか? 言い換えると (図 8 の下にあるモリソンの構造の 1 つとの類推によって)、コードが次のようになっているとします。
public sealed class Singleton
{
private static Singleton value;
private static object syncRoot = new object();
public static Singleton Value
{
get
{
Singleton register = value; // read the field into a register
if (value == null) // re-read the field (for some unknown reason)
{
lock (syncRoot)
{
if (value == null)
{
Singleton newVal = new Singleton();
Thread.MemoryBarrier();
value = register = newVal; // update the register and the field
}
}
}
return register; // return the reference from the register
}
}
}
2 番目のコード サンプルでは、未初期化フィールドをレジスタに読み取った直後にスレッドが横取りされ、他のスレッドがフィールドを初期化した後でスレッドが再開された場合、プロパティは null を返します。誰かがコードでこのように記述した場合 ( というローカル変数を使用register
)、壊れていると思います。しかし、「コード モーション」は、実際にはそうでなくても、理論的には、(コード内の) 最初のサンプルを 2 番目のサンプル (実行用) に効果的に変えることができるでしょうか? 私は信じているvolatile
これに対して保護します。また、値をレジスタにコピーした後に実際のシステムがフィールドを再読み取りする理由はわかりませんが、それは私が推進しているポイントではありません。私の質問は本当に次のように要約されると思います: ECMA メモリ モデルの下で、(「コード モーション」の問題として) 無効になる上記の変換 (最初のサンプルを 2 番目のサンプルに変換する) について何かありますか?
2 番目のコード サンプルでは、シングル スレッドの観点から、レジスタが読み取られるまでに、レジスタは確実にフィールドと同じ参照を保持します。しかし、スレッド共有フィールドを見ている可能性があることはわかっています。コードモーションがそのフィールドの読み取りを導入することが有効である場合、フィールドの読み取りで異なる値が表示される可能性に遭遇しませんか?レジスターによって現在保持されているものから?そして、volatile
これが問題になることを示すもの (のようなもの) がない場合、コードモーションがこのようにフィールドと (仮想の) レジスタの読み取りをインターリーブするのを実際に防止するものは何か(そうする理由がない場合を除く) ? lock
(およびその組み込みMemoryBarrier
) は、(おそらく) レジスター (レジスターを表すローカル変数ではなく、本物のレジスター) が古くなる可能性があることを通知する必要がありますが、スレッドがlock
(先行する繰り返し読み取りのために) に決して入らない場合はどうなるでしょうか?
.NET メモリ モデルはより強力な保証を提供するため、これをテストするのは明らかに困難です。