3

I have a ConcurrentDictionary which maps a simple type to a list:

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

I can use AddOrUpdate() to cater for both initialization of the list when the first value is added, and addition of subsequent values to the list.

However, the same isn't true for removal. If I do something like:

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

...where my intention is to remove the key completely if the corresponding list no longer has any values (similar to reference counting, in concept), then I'm risking a race condition because someone might have added something to the list right after I checked whether it's empty.

Although I am using a list in this simple example, I usually have a ConcurrentBag or ConcurrentDictionary in such scenarios, and the risk is quite similar.

Is there any way of safely removing a key when the corresponding collection is empty, short of resorting to locks?

4

1 回答 1

1

ConcurrentDictionary的受保护,但您的列表不受保护。如果可以从多个线程访问您的列表(我假设是这种情况),您需要在对列表的所有访问周围使用锁定,或者您需要使用不同的构造。

调用函数后,TryGetValueRemove会多次访问该列表 - 由于List<T>多线程不安全,您将面临各种线程问题的风险。

如果您在 中使用嵌套的 ConcurrentDictionaries dict,您只会遇到删除非空内容的问题 - 正如您所写的,在您检查其大小后,可能会将一个项目添加到嵌套的 ConcurrentDictionary 中。删除嵌套列表/字典本身是线程安全的:包含dict是 a ConcurrentDictionary,它将安全地处理删除项目。然而,如果你想保证一个列表/字典只有在它为空时才被删除,你必须在整个操作中使用一个锁。

这是因为容器dict和嵌套列表/字典是两种不同的结构,触摸一个对另一个没有影响 - 如果您需要整个多步骤操作是原子的,您必须确保只有一个线程可以尝试一次做。

您的代码将是这样的:

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

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

同样,如果您使用的是不受保护的构造(例如 aList<T>那么您必须在对该列表的每次访问时使用锁定。

于 2014-12-04T15:18:44.227 回答