6

我使用 aConcurrentDictioanry<string, HashSet<string>>跨多个线程访问一些数据。

我在这篇文章(向下滚动)中读到该方法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

4 回答 4

8

单独锁定 duringAddOrUpdate无济于事 -每次从 set 中读取时,您仍然需要锁定。

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

您不需要在其中实现所有内容ISet<T>- 只需您实际需要的部分。

然后,您将在应用程序代码中创建一个ConcurrentDictionary<string, ConcurrentSet<string>>,然后您就离开了 - 无需锁定。

于 2012-09-13T20:54:16.407 回答
4

您需要修复此代码,它会产生大量垃圾。即使不需要,您也可以创建一个新的 HashSet。使用另一个重载,即接受valueFactory委托的重载。所以 HashSet 仅在字典中不存在键时创建。

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

于 2012-09-13T21:15:43.090 回答
0

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

于 2012-09-13T21:00:41.073 回答
0

似乎更好的答案是使用 Lazy,根据这篇关于传递委托的方法的文章。

还有一篇关于延迟加载添加委托的好文章

于 2017-06-06T13:44:44.520 回答