我有一个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,风险非常相似。
当相应的集合为空时,是否有任何方法可以安全地删除密钥,而不是使用锁?
答案 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>
,那么您必须使用锁定对该列表的每次访问。