辞書 "ContainsKey"のダブルチェックロック
-
14-11-2019 - |
質問
私のチームは現在この問題を議論しています。
問題のコードはの行に沿ったものです。
if (!myDictionary.ContainsKey(key))
{
lock (_SyncObject)
{
if (!myDictionary.ContainsKey(key))
{
myDictionary.Add(key,value);
}
}
}
.
私が見たことのある投稿のいくつかは、これが大きなNOのうち(TryGetValueを使用するとき)かもしれないと言っています。"ContainsKey"がキーコレクションで繰り返さないので、私たちのチームのメンバーはOKですが、o(1)のハッシュコードを介して鍵が含まれているかどうかを確認します。それゆえ、彼らはここに危険がないと主張します。
この問題に関してあなたの正直な意見を得たいと思います。
解決
これをしないでください。安全ではありません。
別のスレッドがContainsKey
を呼び出す間に、1つのスレッドからAdd
を呼び出すことができます。これは単にDictionary<TKey, TValue>
ではサポートされていません。Add
がバケットなどを再割り当てする必要がある場合は、が想像することができます。それはあなたが厄介な効果を見ないような方法で書かれていますが、私はそれに頼っていないような方法で書かれています。
それはまだそれに対して議論していますが、フィールドへの単純な読み取り/書き込みのためのダブルチェックロックを使用しているものです - として明示的に説明されているAPIを呼び出すための別のものです。複数の同時呼び出しに安全であること。
.NET 4、 ConcurrentDictionary
今後の方法。それ以外の場合は、アクセスすべてのアクセスをロックしてください。
他のヒント
マルチスレッド環境にいる場合は、並行変復管を使用することをお勧めします。私は数ヶ月前にブログを付けました、あなたは便利な記事を見つけるかもしれません: http://colinmackay.co.uk/blog/2011/03/24/darelallisation-in-net-4-0-the-concurrent-dictionary/
このコードは正しくありません。Dictionary<TKey, TValue>
型は、同時読み書き操作をサポートしていません。Add
メソッドがロック内で呼び出されても、ContainsKey
はそうではありません。したがって、同時読み取り/書き込みルールの違反を容易に許可し、インスタンスの破損につながることができます
スレッドセーフには見えませんが、それを失敗させるのは難しいでしょう。
繰り返しvs hash lookup引数が保持されていない、インスタンスのハッシュ衝突がある可能性があります。
この辞書がめったに書かれて読まれることがめったにない場合は、書き込み時に辞書全体を置き換えることによって安全なダブルロックを使用します。これは、頻繁に頻繁にするために書き込みをまとめることができれば特に効果的です。
たとえば、これはタイプに関連付けられているスキーマオブジェクトを取得しようとする方法のカットダウンバージョンであり、できません。辞書全体をコピーする必要がある回数を最小限に抑えるために、指定されたタイプと同じアセンブリ内で:
public static Schema GetSchema(Type type)
{
if (_schemaLookup.TryGetValue(type, out Schema schema))
return schema;
lock (_syncRoot) {
if (_schemaLookup.TryGetValue(type, out schema))
return schema;
var newLookup = new Dictionary<Type, Schema>(_schemaLookup);
foreach (var t in type.Assembly.GetTypes()) {
var newSchema = new Schema(t);
newLookup.Add(t, newSchema);
}
_schemaLookup = newLookup;
return _schemaLookup[type];
}
}
.
だからこの場合の辞書は、スキーマが必要なタイプのアセンブリがあるのと同じくらい多くの場合、再構築されます。残りのアプリケーションライフタイムの場合、辞書アクセスはロックフリーになります。辞書のコピーは、アセンブリのワンタイムの初期化コストになります。ポインタ書き込みがアトミックであるため、辞書スワップはスレッドセーフです。参照全体が一度に切り替わります。
他の状況でも同様の原則を適用できます。