我使用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
语句中以确保所有内容都是线程安全的?
答案 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)