AddOrUpdate时锁定ConcurrentDictionary?

时间:2012-09-13 20:48:12

标签: c# .net thread-safety concurrentdictionary

我使用ConcurrentDictioanry<string, HashSet<string>>来访问许多线程中的一些数据。

我在this article(向下滚动)中读到方法AddOrUpdate未在锁中执行,因此可能会危及线程安全。

我的代码如下:

//keys and bar are not the concern here
ConcurrentDictioanry<string, HashSet<string>> foo = new ...;
foreach(var key in keys) {
    foo.AddOrUpdate(key, new HashSet<string> { bar }, (key, val) => {
        val.Add(bar);
        return val;
    });
}

我是否应将AddOrUpdate调用括在lock语句中以确保所有内容都是线程安全的?

4 个答案:

答案 0 :(得分:6)

AddOrUpdate期间自行锁定无济于事 - 每次阅读时,您仍需要锁定

如果您要将此集合视为线程安全的,那么您确实需要这些值也是线程安全的。理想情况下,您需要ConcurrentSet。现在框架内不存在(除非我遗漏了一些东西),但你可以创建自己的ConcurrentSet<T>使用ConcurrentDictionary<T, int>(或者你喜欢的任何TValue)作为它基础数据结构。基本上你忽略了字典中的值,只是将键的存在视为重要部分。

您不需要在ISet<T>内实现所有内容 - 只需要实际需要的位数。

然后,您在应用程序代码中创建一个ConcurrentDictionary<string, ConcurrentSet<string>>,而您就不再需要锁定了。

答案 1 :(得分:4)

你需要修复这段代码,它会造成很多垃圾。即使不需要,也可以创建新的HashSet。使用另一个重载,即接受 valueFactory 委托的重载。因此,只有在词典中尚未出现时才会创建HashSet。

如果多个线程同时尝试添加 key 的相同值并且它不存在,则可能会多次调用 valueFactory 。赔率非常低,但不是零。只使用其中一个哈希集。没问题,创建HashSet没有可能导致线程故障的副作用,额外的副本只会被垃圾收集。

答案 2 :(得分:0)

文章指出add委托不在字典的锁中执行,并且你获得的元素可能不是add委托在该线程中创建的元素。这不是线程安全问题;字典的状态将是一致的,并且所有调用者将获得相同的实例,即使为每个实例创建了一个不同的实例(除了一个之外的所有实例都被删除)。

答案 3 :(得分:0)

似乎更好的答案是使用Lazy,对于传递委托的方法,按照article

还有一篇关于Lazy加载add委托的好文章Here