什么时候应该使用 ConcurrentDictionary 和 Dictionary?

When should I use ConcurrentDictionary and Dictionary?

我总是对选择其中的哪一个感到困惑。正如我所见,如果我想要两种数据类型作为 KeyValue,我会在 List 上使用 Dictionary,这样我可以很容易地通过它的 key 找到一个值但是我总是很困惑我应该使用 ConcurrentDictionary 还是 Dictionary?

在你因为我没有对此进行太多研究而对我大发雷霆之前,我已经尝试过了,但似乎 google 在 DictionaryConcurrentDictionary 上并没有真正得到任何东西,但是每个人身上都有一些东西。

我以前问过一个朋友,但他们只说:"use ConcurrentDictionary if you use your dictionary a lot in code"我真的不想纠缠他们更详细地解释它。任何人都可以对此进行扩展吗?

当您需要跨多个线程(即多线程)访问字典时,

ConcurrentDictionary 很有用。原版 Dictionary 对象不具备此功能,因此只能以 single-threaded 方式使用。

使用 ConcurrentDictionary 而不是常规 Dictionary 的最大原因是线程安全。如果您的应用程序将获得多个线程同时使用同一个字典,您需要 thread-safe ConcurrentDictionary,当这些线程正在写入或构建字典时尤其如此。

在没有 multi-threading 的情况下使用 ConcurrentDictionary 的缺点是开销。所有那些允许它 thread-safe 的函数仍然存在,所有的锁和检查仍然会发生,占用处理时间并使用额外的内存。

“如果您在代码中经常使用词典,请使用 ConcurrentDictionary”是一种模糊的建议。我不怪你造成混乱。

ConcurrentDictionary 主要用于从多线程(或异步任务)更新字典的环境。如果它来自单个线程,您可以从任意多的代码中使用标准 Dictionary ;)

如果您查看 ConcurrentDictionary 上的方法,您会发现一些有趣的方法,例如 TryAddTryGetValueTryUpdateTryRemove

例如,考虑一个典型的模式,您可能会看到使用普通 Dictionary class.

// There are better ways to do this... but we need an example ;)
if (!dictionary.ContainsKey(id))
    dictionary.Add(id, value);

这有一个问题,在检查它是否包含密钥和调用 Add 之间,不同的线程可以使用相同的 id 调用 Add。当这个线程调用Add时,它会抛出一个异常。方法 TryAdd 会为您处理,并将 return 一个 true/false 告诉您它是否添加了它(或者该键是否已经在字典中)。

因此,除非您在 multi-threaded 代码部分工作,否则您可能只使用标准 Dictionary class。话虽如此,理论上您可以使用锁来防止并发访问字典; "Dictionary locking vs. ConcurrentDictionary".

中已经解决了这个问题

A ConcurrentDictionary 当你想要一个可以被多个线程同时安全访问的 high-performance 字典时很有用。与受 lock 保护的标准 Dictionary 相比,由于其细粒度锁定实现,它在大量使用时效率更高。 ConcurrentDictionary 不是所有线程都竞争一个锁,而是在内部维护多个锁,以这种方式最大限度地减少争用,并限制成为瓶颈的可能性。

尽管有这些不错的特性,但使用 ConcurrentDictionary 是最佳选择的场景实际上很少。有两个原因:

  1. ConcurrentDictionary 提供的 thread-safety 保证仅限于对其内部状态的保护。而已。如果你想做一些稍微 non-trivial 的事情,比如更新字典 另一个变量作为原子操作,你就不走运了。 ConcurrentDictionary 不支持这种情况。甚至不支持保护它包含的元素(如果它们是可变对象)。如果您尝试使用 AddOrUpdate 方法更新其中一个值,字典将受到保护,但值不会。 Update在此上下文中的意思是用另一个值替换现有值,而不是修改现有值

  2. 每当您觉得使用 ConcurrentDictionary 时,通常都有更好的选择。不涉及共享状态的替代方案,这就是 ConcurrentDictionary 的本质。无论它的锁定方案多么有效,它都很难击败根本没有共享状态的架构,每个线程都在不干扰其他线程的情况下做自己的事情。遵循这一原则的常用库是 PLINQ and the TPL Dataflow 库。下面是一个 PLINQ 示例:

Dictionary<string, Product> dictionary = productIDs
    .AsParallel()
    .Select(id => GetProduct(id))
    .ToDictionary(product => product.Barcode);

您可以相信 PLINQ 可以使用更有效的策略生成字典,包括对初始工作负载进行分区,并将每个分区分配给不同的任务,而不是预先创建一个字典,然后让多个线程同时用值填充它工作线程。单个线程最终会聚合部分结果,并填充字典。

上面接受的答案是正确的。然而,值得一提的是,如果字典没有被修改,即它只被读取,而不管线程的数量,那么 Dictionary<TKey,TValue> 是首选,因为不需要同步。

例如在 Dictionary<TKey,TValue> 中缓存配置,仅在启动时填充一次,并在整个应用程序的整个生命周期中使用。

When to use a thread-safe collection : ConcurrentDictionary vs. Dictionary

If you are only reading key or values, the Dictionary<TKey,TValue> is faster because no synchronization is required if the dictionary is not being modified by any threads.