16

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

有问题的代码类似于

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

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

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

4

5 回答 5

28

不要这样做。这不安全。

您可能ContainsKey从一个线程调用,而另一个线程调用Add. 根本不支持Dictionary<TKey, TValue>。如果Add需要重新分配存储桶等,我可以想象你得到一些非常奇怪的结果,或者异常。它可能以您看不到任何不良影响的方式编写,但我不想依赖它。

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

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

于 2011-05-16T14:23:32.540 回答
6

如果您在多线程环境中,您可能更喜欢使用 ConcurrentDictionary。几个月前我在博客上写过,你可能会发现这篇文章很有用:http ://colinmackay.co.uk/blog/2011/03/24/parallelisation-in-net-4-0-the-concurrent-dictionary /

于 2011-05-16T14:22:58.083 回答
6

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

于 2011-05-16T14:23:47.410 回答
1

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

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

于 2011-05-16T14:23:57.030 回答
0

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

例如,这是我们使用的方法的简化版本,它试图获取与类型关联的模式对象,如果不能,则继续为在同一类型中找到的所有类型创建模式对象将程序集作为指定类型以最小化必须复制整个字典的次数:

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

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

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

于 2017-11-06T21:44:13.497 回答