6

マルチスレッドアプリケーションで使用するコンカレントディクショナリにクラスのインスタンスをキャッシュする次のコードがあります。

簡単に言うと、id パラメーターを使用してクラスをインスタンス化すると、まず、指定された id を持つ privateclass のインスタンスが辞書に存在するかどうかがチェックされ、存在しない場合は privateclass のインスタンスが作成されます (これには長い時間がかかり、場合によっては数秒かかります)。将来の使用のために辞書に追加します。

public class SomeClass
{
    private static readonly ConcurrentDictionary<int, PrivateClass> SomeClasses =
        new ConcurrentDictionary<int, PrivateClass>();

    private readonly PrivateClass _privateClass;

    public SomeClass(int cachedInstanceId)
    {
        if (!SomeClasses.TryGetValue(cachedInstanceId, out _privateClass))
        {
            _privateClass = new PrivateClass(); // This takes long time
            SomeClasses.TryAdd(cachedInstanceId, _privateClass);
        }
    }

    public int SomeCalculationResult()
    {
        return _privateClass.CalculateSomething();
    }

    private class PrivateClass
    {
        internal PrivateClass()
        {
            // this takes long time
        }

        internal int CalculateSomething()
        {
            // Calculates and returns something
        }
    }
}

私の質問は、このコードをスレッド セーフにするために、外部クラス コンストラクターの生成と割り当ての部分にロックを追加する必要がありますか、それともそのままでよいのでしょうか?

アップデート:

SLaks さんの提案の後、 ConcurrentDictionary のGetOrAdd()メソッドをLazyの組み合わせで使用しようとしましたが、残念ながらコンストラクターがPrivateClass複数回呼び出されました。テスト コードについては、https://gist.github.com/3500955を参照してください。

更新 2: ここで最終的な解決策を確認できます: https://gist.github.com/3501446

4

3 に答える 3

12

あなたは悪用していますConcurrentDictionary

マルチスレッド コードでは、項目の存在を確認して、存在しない場合は追加しないでください。
2 つのスレッドがそのコードを同時に実行すると、両方ともコードを追加することになります。

一般に、この種の問題には 2 つの解決策があります。そのすべてのコードをロックでラップするか、1 つのアトミック操作で全体を再設計することができます。

ConcurrentDictionaryこの種のシナリオ向けに設計されています。

あなたは単に呼び出す必要があります

 _privateClass = SomeClasses.GetOrAdd(cachedInstanceId, key => new PrivateClass());
于 2012-08-28T15:59:23.487 に答える
5

ロックは必要ありませんが、あなたがしていることはスレッドセーフではありません。最初に辞書でアイテムの存在を確認してから必要に応じて追加するのではなく、を使用ConcurrentDictionary.GetOrAdd()してすべてを1つのアトミック操作で実行する必要があります。

そうしないと、通常の辞書で発生するのと同じ問題に直面することになりSomeClassesます。存在を確認した後、挿入する前に、別のスレッドがエントリを追加する可能性があります。

于 2012-08-28T16:02:30.557 に答える
3

ConcurrentDictionary と Lazy<T> を使用したhttps://gist.github.com/3500955のサンプル コードは正しくありません。

    private static readonly ConcurrentDictionary<int, PrivateClass> SomeClasses =
        new ConcurrentDictionary<int, PrivateClass>();
    public SomeClass(int cachedInstanceId)
    {
        _privateClass = SomeClasses.GetOrAdd(cachedInstanceId, (key) => new Lazy<PrivateClass>(() => new PrivateClass(key)).Value);
    }

..次のようにする必要があります。

    private static readonly ConcurrentDictionary<int, Lazy<PrivateClass>> SomeClasses =
        new ConcurrentDictionary<int, Lazy<PrivateClass>>();
    public SomeClass(int cachedInstanceId)
    {
        _privateClass = SomeClasses.GetOrAdd(cachedInstanceId, (key) => new Lazy<PrivateClass>(() => new PrivateClass(key))).Value;
    }

ConcurrentDictionary<TKey, Lazy<TVal>> を使用する必要があり、ConcurrentDictionary<TKey, TVal> は使用しないでください。ポイントは、GetOrAdd() から正しい Lazy オブジェクトが返されたにのみ、Lazy の Value にアクセスすることです。Lazy オブジェクトの Value を GetOrAdd 関数に送信すると、それを使用する目的全体が無効になります。

編集: ああ - https://gist.github.com/mennankara/3501446 :)

于 2015-01-12T12:49:31.687 に答える