16

私のチームは現在この問題について議論しています。

問題のコードは、

if (!myDictionary.ContainsKey(key))
{
    lock (_SyncObject)
    {
        if (!myDictionary.ContainsKey(key))
        {
            myDictionary.Add(key,value);
        }
    }
}

私が見た投稿のいくつかは、これは大きなNO NOかもしれないと言っています(TryGetValueを使用している場合)。しかし、私たちのチームのメンバーは、「ContainsKey」はキーコレクションを反復処理せず、O(1)のハッシュコードを介してキーが含まれているかどうかをチェックするため、問題ないと述べています。したがって、彼らはここに危険はないと主張します。

この件に関して、正直なご意見をお聞かせください。

4

5 に答える 5

28

これをしないでください。安全ではありません。

ContainsKey別のスレッドがを呼び出している間に、あるスレッドから呼び出している可能性がありますAdd。それは単にによってサポートされていませんDictionary<TKey, TValue>Addバケットなどを再割り当てする必要がある場合は、非常に奇妙な結果や例外が発生する可能性があると思います。不快な影響が出ないように書かれているのかもしれませんが、頼りたくありません

これは、フィールドへの単純な読み取り/書き込みにダブルチェックロックを使用することの1つですが、それでも反対します。複数の同時呼び出しに対して安全ではないと明示的に記述されているAPIを呼び出すことは別のことです。

.NET 4を使用している場合ConcurrentDictionaryは、おそらく前進する方法です。それ以外の場合は、すべてのアクセスをロックします。

于 2011-05-16T14:23:32.540 に答える
6

マルチスレッド環境を使用している場合は、ConcurrentDictionaryの使用を検討することをお勧めします。私は数ヶ月前にそれについてブログを書きました、あなたは記事が役に立つと思うかもしれません:http: //colinmackay.co.uk/blog/2011/03/24/parallelisation-in-net-4-0-the-concurrent-dictionary //

于 2011-05-16T14:22:58.083 に答える
6

このコードは正しくありません。このDictionary<TKey, TValue>タイプは、読み取りと書き込みの同時操作をサポートしていません。あなたのAddメソッドがロック内で呼び出されても、そうでContainsKeyはありません。したがって、読み取り/書き込みの同時ルールの違反を簡単に許容し、インスタンスの破損につながります

于 2011-05-16T14:23:47.410 に答える
1

スレッドセーフには見えませんが、失敗させるのはおそらく難しいでしょう。

反復vsハッシュルックアップ引数は成り立たず、たとえばハッシュ衝突が発生する可能性があります。

于 2011-05-16T14:23:57.030 に答える
0

この辞書がめったに書かれず、頻繁に読まれる場合、私はしばしば、書き込み時に辞書全体を置き換えることによって安全なダブルロックを採用します。これは、書き込みをまとめてバッチ処理して頻度を減らすことができる場合に特に効果的です。

たとえば、これは、型に関連付けられたスキーマオブジェクトを取得しようとするメソッドの縮小版であり、取得できない場合は、先に進んで、同じ場所で見つかったすべての型のスキーマオブジェクトを作成します。ディクショナリ全体をコピーする必要がある回数を最小限に抑えるための、指定されたタイプとしてのアセンブリ:

    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];
        }
    }

したがって、この場合のディクショナリは、スキーマを必要とするタイプのアセンブリと同じ回数だけ再構築されます。アプリケーションの残りの存続期間中、ディクショナリアクセスはロックフリーになります。ディクショナリのコピーは、アセンブリの1回限りの初期化コストになります。ポインタの書き込みはアトミックであり、参照全体が一度に切り替えられるため、ディクショナリのスワップはスレッドセーフです。

他の状況でも同様の原則を適用できます。

于 2017-11-06T21:44:13.497 に答える