字典“ContainsKey”上的双重检查锁定

发布于 2024-11-07 05:04:37 字数 396 浏览 7 评论 0 原文

我的团队目前正在讨论这个问题。

有问题的代码类似于

if (!myDictionary.ContainsKey(key))
{
    lock (_SyncObject)
    {
        if (!myDictionary.ContainsKey(key))
        {
            myDictionary.Add(key,value);
        }
    }
}

我见过的一些帖子说这可能是一个很大的“不”(当使用 TryGetValue 时)。然而我们团队的成员说这没问题,因为“ContainsKey”不会迭代密钥集合,而是通过 O(1) 的哈希码检查密钥是否包含。因此他们声称这里没有危险。

我想听听您对这个问题的诚实意见。

My team is currently debating this issue.

The code in question is something along the lines of

if (!myDictionary.ContainsKey(key))
{
    lock (_SyncObject)
    {
        if (!myDictionary.ContainsKey(key))
        {
            myDictionary.Add(key,value);
        }
    }
}

Some of the posts I've seen say that this may be a big NO NO (when using TryGetValue). Yet members of our team say it is ok since "ContainsKey" does not iterate on the key collection but checks if the key is contained via the hash code in O(1). Hence they claim there is no danger here.

I would like to get your honest opinions regarding this issue.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

反目相谮 2024-11-14 05:04:37

不要这样做。这不安全。

您可以从一个线程调用 ContainsKey,而另一个线程调用 AddDictionary 根本不支持这一点。如果Add需要重新分配存储桶等,我可以想象您可能得到一些非常奇怪的结果,或者异常。它可能的编写方式使您不会看到任何令人讨厌的效果,但我不想依赖它。

使用双重检查锁定对字段进行简单的读/写是一回事,尽管我仍然反对它 - 调用已被明确描述为不安全的 API 是另一回事用于多个并发调用。

如果您使用的是 .NET 4,ConcurrentDictionary 可能是前进的方向。否则,只需锁定每个访问即可。

Don't do this. It's not safe.

You could be calling ContainsKey from one thread while another thread calls Add. That's simply not supported by Dictionary<TKey, TValue>. If Add 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.

一花一树开 2024-11-14 05:04:37

如果您处于多线程环境中,您可能更愿意考虑使用 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/

慕烟庭风 2024-11-14 05:04:37

此代码不正确。 Dictionary 类型不支持同时读写操作。即使您的 Add 方法是在锁内调用的,但 ContainsKey 却不是。因此,它很容易违反同时读/写规则,并导致实例损坏

This code is incorrect. The Dictionary<TKey, TValue> type does not support simultaneous read and write operations. Even though your Add method is called within the lock the ContainsKey is not. Hence it easily allows for a violation of the simultaneous read / write rule and will lead to corruption in your instance

苍风燃霜 2024-11-14 05:04:37

它看起来不是线程安全的,但可能很难让它失败。

迭代与哈希查找参数不成立,例如可能存在哈希冲突。

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.

如梦初醒的夏天 2024-11-14 05:04:37

如果这本字典很少被写入并且经常被读取,那么我经常通过在写入时替换整个字典来采用安全的双重锁定。如果您可以批量写入以降低写入频率,那么这将特别有效。

例如,这是我们使用的方法的简化版本,该方法尝试获取与类型关联的模式对象,如果不能,那么它会继续并为它在同一类型中找到的所有类型创建模式对象。程序集作为指定的类型,以最大限度地减少整个字典的复制次数:

    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];
        }
    }

因此,在这种情况下,字典将被重建,最多与需要模式的类型的程序集一样多。在应用程序生命周期的剩余时间内,字典访问将是无锁的。字典副本成为程序集的一次性初始化成本。字典交换是线程安全的,因为指针写入是原子的,因此整个引用会立即切换。

您也可以在其他情况下应用类似的原则。

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:

    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];
        }
    }

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文