安全地从并发字典中删除列表映射

本文关键字:删除 删除列 列表 映射 字典 并发 安全 | 更新日期: 2023-09-27 18:33:11

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

当相应的集合为空时,有没有办法安全地删除密钥,而不是诉诸锁?

安全地从并发字典中删除列表映射

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

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

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

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

你的代码是这样的:

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

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