对字典"ContainsKey"进行双重检查锁定
-
14-11-2019 - |
题
我的团队目前正在讨论这个问题。
有问题的代码是沿着
if (!myDictionary.ContainsKey(key))
{
lock (_SyncObject)
{
if (!myDictionary.ContainsKey(key))
{
myDictionary.Add(key,value);
}
}
}
我见过的一些帖子说这可能是一个很大的NO NO(当使用TryGetValue时)。然而,我们团队的成员说这是可以的,因为"ContainsKey"不会迭代密钥集合,而是通过o(1)中的哈希代码检查密钥是否包含。因此,他们声称这里没有危险。
我想得到你关于这个问题的诚实意见。
解决方案
别这样这不安全。
你可以打电话来 ContainsKey
从一个线程,而另一个线程调用 Add
.这根本不支持 Dictionary<TKey, TValue>
.如果 Add
需要重新分配桶等,我可以想象你 可以 得到一些非常奇怪的结果,或者一个例外。它 五月 已经写在这样一种方式,你没有看到任何讨厌的影响,但我不想依赖它。
使用双重检查锁定对字段进行简单的读取/写入是一回事,尽管我仍然反对它-对已明确描述为的API进行调用是另一回事 不是 对于多个并发调用是安全的。
如果你在.NET4上, ConcurrentDictionary
可能是前进的方向。否则,只需锁定每个访问。
其他提示
如果您处于多线程环境中,您可能更愿意查看使用套同时的dictionary。我几个月前博成了它,你可能会发现这篇文章有用: http://colinmackay.co.uk/blog/2011/03/24/parallelisation-in-nt-4-0-the-concurrent-dictionary/
此代码不正确。Dictionary<TKey, TValue>
类型不支持同时读写操作。即使在锁定中调用Add
方法,则ContainsKey
也不是。因此它很容易允许违反同时读/写规则,并将导致实例中的损坏
它看起来并不是线程安全的,但它可能很难让它失败。
迭代与哈希查找参数不包含,可能有一个散列碰撞。
如果这个词典很少写入并经常读取,那么我经常通过替换写入整个字典来使用安全的双重锁定。如果您可以一起写入以使其少频繁,这是特别有效的。 例如,这是我们使用的方法的剪切版本,该方法尝试获取与类型关联的架构对象,如果不能,那么它会导致它找到的所有类型的模式对象在与指定类型的同一组件中最小化整个字典必须复制的次数:
public static Schema GetSchema(Type type)
{
if (_schemaLookup.TryGetValue(type, out Schema schema))
return schema;
lock (_syncRoot) {
if (_schemaLookup.TryGetValue(type, out schema))
return schema;
var newLookup = new Dictionary<Type, Schema>(_schemaLookup);
foreach (var t in type.Assembly.GetTypes()) {
var newSchema = new Schema(t);
newLookup.Add(t, newSchema);
}
_schemaLookup = newLookup;
return _schemaLookup[type];
}
}
.
所以在这种情况下,在这种情况下将重建字典,并且多次,因为有多次有需要模式的组件。对于其余的应用程序生命周期,字典访问将是无锁的。字典副本成为组装的一次性初始化成本。字典交换是线程安全的,因为指针写入原子,因此整个参考会立即切换。
您也可以在其他情况下应用类似的原理。