安全地从ConcurrentDictionary中删除列表映射

时间:2014-12-04 15:07:47

标签: c# concurrency race-condition concurrentdictionary

我有一个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,风险非常相似。

当相应的集合为空时,是否有任何方法可以安全地删除密钥,而不是使用锁?

1 个答案:

答案 0 :(得分:2)

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

TryGetValue函数中调用Remove后,您多次访问该列表 - 由于List<T>对多线程不安全,因此存在各种线程问题的风险。

如果你在dict中使用嵌套的ConcurrentDictionaries,你只会遇到删除不为空的东西的问题 - 正如你所写的那样,将一个项添加到嵌套的ConcurrentDictionary中是可能的检查它的大小后。删除嵌套列表/字典本身是线程安全的:包含dict的是ConcurrentDictionary,并且它可以安全地删除项目。但是,如果您想保证只有列表/词典为空时才会被删除,您必须使用锁定整个操作。

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

您的代码将是这样的:

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

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

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