次のような競合状態を回避することができると思います。
dictionary[i] = dictionary[i] + 1
アトミックではありません。値を取得してインクリメントした後、割り当てdictionary
先が変わる可能性があります。
次のコードを想像してください。
public Dictionary<int, int> dictionary = new Dictionary<int, int>();
public void Increment()
{
int newValue = dictionary[0] + 1;
//meanwhile, right now in another thread: dictionary = new Dictionary<int, int>();
dictionary[0] = newValue; //at this point, "dictionary" is actually pointing to a whole new instance
}
彼らが持っているローカル変数の割り当てにより、条件を回避するために次のようになります。
public void IncrementFix()
{
var dictionary2 = dictionary;
//in another thread: dictionary = new Dictionary<int, int>();
//this is OK, dictionary2 is still pointing to the ORIGINAL dictionary
int newValue = dictionary2[0] + 1;
dictionary2[0] = newValue;
}
すべてのスレッドセーフ要件を完全に満たすわけではないことに注意してください。たとえば、この場合、値のインクリメントを開始しますがdictionary
、クラスの参照はまったく新しいインスタンスに変更されています。ただし、より高いレベルのスレッド セーフが必要な場合は、独自の積極的な同期/ロックを実装する必要がありますが、これは通常、コンパイラの最適化の範囲外です。ただし、これは、私が知る限り、実際には処理に大きな影響を与えることはなく (もしあれば)、この状態を回避します。dictionary
これは、プロパティの場合に特に当てはまる可能性がありますあなたの例のようなフィールドではありません。その場合、プロパティゲッターを2回解決しないことは間違いなく最適化です。(ひょっとして、実際のコードは、投稿したフィールドではなく、辞書のプロパティを使用していますか?)
編集:まあ、簡単な方法の場合:
public void IncrementDictionary()
{
dictionary[0]++;
}
LINQPad から報告される IL は次のとおりです。
IL_0000: nop
IL_0001: ldarg.0
IL_0002: ldfld UserQuery.dictionary
IL_0007: dup
IL_0008: stloc.0
IL_0009: ldc.i4.0
IL_000A: ldloc.0
IL_000B: ldc.i4.0
IL_000C: callvirt System.Collections.Generic.Dictionary<System.Int32,System.Int32>.get_Item
IL_0011: ldc.i4.1
IL_0012: add
IL_0013: callvirt System.Collections.Generic.Dictionary<System.Int32,System.Int32>.set_Item
IL_0018: nop
IL_0019: ret
完全にはわかりませんが (私は IL ウィズではありません)、dup
呼び出しは基本的にスタック上の同じ辞書参照を 2 倍にするので、get 呼び出しと set 呼び出しの両方が同じ辞書を指すことに関係なく、そう思います。おそらくこれは、ILSpy が C# コードとして表現する方法です (少なくとも動作に関しては、多かれ少なかれ同じです)。おもう。私が言ったように、私はまだ私の手の甲のようにILを知らないので、私が間違っている場合は修正してください.
編集: 実行する必要がありますが、その最終的な要点は次のとおりです:++
と+=
はアトミック操作ではなく、実際には C# で示されているよりも実行される命令の方がかなり複雑です。そのため、get/increment/set の各ステップが同じディクショナリ インスタンスで実行されることを確認するために (C# コードから期待および要求されるように)、ディクショナリへのローカル参照が作成され、フィールドの実行が回避されます。 get" 操作を 2 回実行すると、新しいインスタンスが指される可能性があります。ILSpy は、インデックス付き += 操作に関連するすべての作業がそれ次第であることを示しています。