安全地从并发字典中删除列表映射
本文关键字:删除 删除列 列表 映射 字典 并发 安全 | 更新日期: 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>
那么您必须在对该列表的每个访问周围使用锁定。