3

単純な型をリストにマップする ConcurrentDictionary があります。

var dict = new ConcurrentDictionary<string, List<string>>();

AddOrUpdate()を使用して、最初の値が追加されたときのリストの初期化と、リストへの後続の値の追加の両方に対応できます。

ただし、削除については同じではありません。私が次のようなことをした場合:

public void Remove(string key, string value)
{
    List<string> list;
    var found = dict.TryGetValue(key, out list);

    if (found)
    {
        list.Remove(value);
        if (list.Count == 0)
        {
            // warning: possible race condition here
            dict.TryRemove(key, out list);
        }
    }
}

...対応するリストに値がなくなった場合にキーを完全に削除することを意図している場合(概念的には参照カウントに似ています)、誰かが直後にリストに何かを追加した可能性があるため、競合状態のリスクがあります空かどうかを確認しました。

この単純な例ではリストを使用していますが、通常、そのようなシナリオでは ConcurrentBag または ConcurrentDictionary を使用しており、リスクは非常に似ています。

対応するコレクションが空の場合、ロックに頼らずにキーを安全に削除する方法はありますか?

4

1 に答える 1

1

あなたConcurrentDictionaryは保護されていますが、あなたのリストは保護されていません。リストに複数のスレッドからアクセスできる場合 (これが当てはまると思います)、リストへのすべてのアクセスに対してロックを使用するか、別の構造を使用する必要があります。

関数を呼び出した後TryGetValueRemoveリストに複数回アクセスしますList<T>。マルチスレッドでは安全ではないため、さまざまなスレッドの問題が発生するリスクがあります。

でネストされた ConcurrentDictionaries を使用するとdict、空でないものを削除するという問題のみが実行されます。あなたが書いたように、サイズを確認した後、ネストされた ConcurrentDictionary に項目が追加される可能性があります。ネストされたリスト/ディクショナリ自体の削除はスレッドセーフです。含まれdictているのは でConcurrentDictionaryあり、アイテムの削除を安全に処理します。ただし、リスト/辞書が空の場合にのみ削除されることを保証したい場合は、操作全体にロックを使用する必要があります。

これは、コンテナーdictとネストされたリスト/辞書が 2 つの異なる構造であり、一方に触れても他方に影響がないためです。マルチステップ操作全体をアトミックにする必要がある場合は、1 つのスレッドだけが試行できるようにする必要があります。一度に行うこと。

コードは次のようになります。

if (found)
{
    lock ( _listLock )
    {
        list.Remove(value);

        if (list.Count == 0)
        {
            // warning: possible race condition here
            dict.TryRemove(key, out list);
        }
    }
}

繰り返しますが、保護されていない構造 ( a など)List<T>を使用している場合は、そのリストへのアクセスごとにロックを使用する必要があります。

于 2014-12-04T15:18:44.227 に答える