字典“ContainsKey”上的双重检查锁定
我的团队目前正在讨论这个问题。
有问题的代码类似于
if (!myDictionary.ContainsKey(key))
{
lock (_SyncObject)
{
if (!myDictionary.ContainsKey(key))
{
myDictionary.Add(key,value);
}
}
}
我见过的一些帖子说这可能是一个很大的“不”(当使用 TryGetValue 时)。然而我们团队的成员说这没问题,因为“ContainsKey”不会迭代密钥集合,而是通过 O(1) 的哈希码检查密钥是否包含。因此他们声称这里没有危险。
我想听听您对这个问题的诚实意见。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
不要这样做。这不安全。
您可以从一个线程调用
ContainsKey
,而另一个线程调用Add
。Dictionary
根本不支持这一点。如果Add
需要重新分配存储桶等,我可以想象您可能得到一些非常奇怪的结果,或者异常。它可能的编写方式使您不会看到任何令人讨厌的效果,但我不想依赖它。使用双重检查锁定对字段进行简单的读/写是一回事,尽管我仍然反对它 - 调用已被明确描述为不安全的 API 是另一回事用于多个并发调用。
如果您使用的是 .NET 4,
ConcurrentDictionary
可能是前进的方向。否则,只需锁定每个访问即可。Don't do this. It's not safe.
You could be calling
ContainsKey
from one thread while another thread callsAdd
. That's simply not supported byDictionary<TKey, TValue>
. IfAdd
needs to reallocate buckets etc, I can imagine you could get some very strange results, or an exception. It may have been written in such a way that you don't see any nasty effects, but I wouldn't like to rely on it.It's one thing using double-checked locking for simple reads/writes to a field, although I'd still argue against it - it's another to make calls to an API which has been explicitly described as not being safe for multiple concurrent calls.
If you're on .NET 4,
ConcurrentDictionary
is probably the way forward. Otherwise, just lock on every access.如果您处于多线程环境中,您可能更愿意考虑使用 ConcurrentDictionary。几个月前我在博客上介绍了它,您可能会发现这篇文章很有用:http://colinmackay.co.uk/blog/2011/03/24/parallelization-in-net-4-0-the-concurrent-dictionary/
If you are in a multithreaded environment, you may prefer to look at using a ConcurrentDictionary. I blogged about it a couple of months ago, you might find the article useful: http://colinmackay.co.uk/blog/2011/03/24/parallelisation-in-net-4-0-the-concurrent-dictionary/
此代码不正确。
Dictionary
类型不支持同时读写操作。即使您的Add
方法是在锁内调用的,但ContainsKey
却不是。因此,它很容易违反同时读/写规则,并导致实例损坏This code is incorrect. The
Dictionary<TKey, TValue>
type does not support simultaneous read and write operations. Even though yourAdd
method is called within the lock theContainsKey
is not. Hence it easily allows for a violation of the simultaneous read / write rule and will lead to corruption in your instance它看起来不是线程安全的,但可能很难让它失败。
迭代与哈希查找参数不成立,例如可能存在哈希冲突。
It doesn't look thread-safe, but it would probably be hard to make it fail.
The iteration vs hash lookup argument doesn't hold, there could be a hash-collision for instance.
如果这本字典很少被写入并且经常被读取,那么我经常通过在写入时替换整个字典来采用安全的双重锁定。如果您可以批量写入以降低写入频率,那么这将特别有效。
例如,这是我们使用的方法的简化版本,该方法尝试获取与类型关联的模式对象,如果不能,那么它会继续并为它在同一类型中找到的所有类型创建模式对象。程序集作为指定的类型,以最大限度地减少整个字典的复制次数:
因此,在这种情况下,字典将被重建,最多与需要模式的类型的程序集一样多。在应用程序生命周期的剩余时间内,字典访问将是无锁的。字典副本成为程序集的一次性初始化成本。字典交换是线程安全的,因为指针写入是原子的,因此整个引用会立即切换。
您也可以在其他情况下应用类似的原则。
If this dictionary is rarely written and often read, then I often employ safe double locking by replacing the entire dictionary on write. This is particularly effective if you can batch writes together to make them less frequent.
For example, this is a cut down version of a method we use that tries to get a schema object associated with a type, and if it can't, then it goes ahead and creates schema objects for all the types it finds in the same assembly as the specified type to minimize the number of times the entire dictionary has to be copied:
So the dictionary in this case will be rebuilt, at most, as many times as there are assemblies with types that need schemas. For the rest of the application lifetime the dictionary accesses will be lock-free. The dictionary copy becomes a one-time initialization cost of the assembly. The dictionary swap is thread-safe because pointer writes are atomic so the whole reference gets switched at once.
You can apply similar principles in other situations as well.