6

あるメソッドでこの C# コードを想像してみてください。

SomeClass.SomeGlobalStaticDictionary = new Dictionary<int, string>()
{
    {0, "value"},
};

ディクショナリにアクセスするために、明示的なメモリ バリアやロックを使用している人は誰もいないとしましょう。

最適化が行われない場合、グローバル ディクショナリは null (初期値) または 1 つのエントリを持つ適切に構築されたディクショナリのいずれかになります。

問題は、Add 呼び出しと SomeGlobalStaticDictionary への割り当ての効果を並べ替えて、他のスレッドが空の null 以外の SomeGlobalStaticDictionary (またはその他の無効な部分的に構築された辞書) を参照できるようにすることはできますか?

SomeGlobalStaticDictionary が揮発性である場合、答えは変わりますか?

http://msdn.microsoft.com/en-us/magazine/jj863136.aspx (およびその 2 番目の部分) を読んだ後、理論的には、ソース コードで 1 つの変数が割り当てられているという理由だけで、他のスレッドが原因で別の方法で表示される可能性があることを学びました。多くの理由。IL コードを見ましたが、問題は、SomGlobalStaticDictionary の割り当ての前に、JIT コンパイラや CPU が Add 呼び出しの効果を他のスレッドに「フラッシュ」しないように許可されているかどうかです。

4

2 に答える 2

6

ローカル変数では、最適化がオンになっていると、コンパイラ(少なくとも時々) 最初に変数に代入し、次にAddオブジェクト初期化子の呼び出し (またはプロパティの設定) を行うコードにコンパイルします。

静的変数またはインスタンス変数を使用すると、異なる動作が見られます。

class Test
{
    static List<int> StaticList = new List<int> { 1 };
    List<int> InstanceList = new List<int> { 2 };
}

次の型初期化子 IL を提供します。

.method private hidebysig specialname rtspecialname static 
        void  .cctor() cil managed
{
  // Code size       21 (0x15)
  .maxstack  2
  .locals init (class [mscorlib]System.Collections.Generic.List`1<int32> V_0)
  IL_0000:  newobj     instance void class [mscorlib]System.Collections.Generic.List`1<int32>::.ctor()
  IL_0005:  stloc.0
  IL_0006:  ldloc.0
  IL_0007:  ldc.i4.1
  IL_0008:  callvirt   instance void class [mscorlib]System.Collections.Generic.List`1<int32>::Add(!0)
  IL_000d:  nop
  IL_000e:  ldloc.0
  IL_000f:  stsfld     class [mscorlib]System.Collections.Generic.List`1<int32> Test::StaticList
  IL_0014:  ret
} // end of method Test::.cctor

そして、次のコンストラクター IL:

.method public hidebysig specialname rtspecialname 
        instance void  .ctor() cil managed
{
  // Code size       29 (0x1d)
  .maxstack  3
  .locals init (class [mscorlib]System.Collections.Generic.List`1<int32> V_0)
  IL_0000:  ldarg.0
  IL_0001:  newobj     instance void class [mscorlib]System.Collections.Generic.List`1<int32>::.ctor()
  IL_0006:  stloc.0
  IL_0007:  ldloc.0
  IL_0008:  ldc.i4.2
  IL_0009:  callvirt   instance void class [mscorlib]System.Collections.Generic.List`1<int32>::Add(!0)
  IL_000e:  nop
  IL_000f:  ldloc.0
  IL_0010:  stfld      class [mscorlib]System.Collections.Generic.List`1<int32> Test::InstanceList
  IL_0015:  ldarg.0
  IL_0016:  call       instance void [mscorlib]System.Object::.ctor()
  IL_001b:  nop
  IL_001c:  ret
} // end of method Test::.ctor

どちらの場合も、フィールドが設定される前にコレクションが設定されます。メモリ モデルの問題がまだないというわけではありませんが、フィールドが空のコレクションを参照するように設定されてから呼び出しが行われるのと同じではありません。Add割り当てスレッドの観点から、割り当てはAdd.

一般に、オブジェクト初期化子とコレクション初期化子の式は、一時変数を使用してオブジェクトを構築することと同等です。そのため、代入で使用する場合、代入が行われる前にプロパティ セッターがすべて呼び出されます。

ただし、オブジェクト/コレクション初期化子の他のスレッドへの可視性に関して特別な保証が与えられているとは思いません仕様に従って「手書き」で書き出すとコードがどのようになるかを想像し、そこから推論することをお勧めします。

静的初期化子とコンストラクターには保証がありますが、主に「一般的な」保証ではなく、.NET の Microsoft 実装内にあります (たとえば、C# 仕様または ECMA 仕様内)。

于 2013-04-22T16:26:04.703 に答える