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

Posted

技术标签:

【中文标题】什么时候应该使用 ConcurrentDictionary 和 Dictionary?【英文标题】:When should I use ConcurrentDictionary and Dictionary? 【发布时间】:2022-01-23 18:17:51 【问题描述】:

我总是对选择其中一个感到困惑。正如我所看到的,如果我想要两种数据类型作为KeyValue,我会使用Dictionary 而不是List,所以我可以通过key 轻松找到一个值,但如果我应该使用ConcurrentDictionaryDictionary?

在你指责我没有对此进行太多研究之前,我已经尝试过了,但似乎谷歌在 DictionaryConcurrentDictionary 上并没有真正得到任何东西,但每个人都有一些东西。

我之前问过一个朋友,但他们只说:“如果你在代码中经常使用字典,请使用ConcurrentDictionary”,我真的不想纠缠他们更详细地解释它。任何人都可以对此进行扩展吗?

【问题讨论】:

那么,在逐个查看了每个对象的信息之后,如何无法回答您何时应该使用每个对象的问题?如果你知道什么时候应该使用Dictionary,什么时候应该使用ConcurrentDictionary,假设你说你已经找到了这些信息,那么你知道什么时候应该使用另一个。 这个名字可以自我解释。当您需要对字典进行并发访问时,您可以使用 ConcurrentDictionary 如果您要从多个线程访问字典,请使用 ConcurrentDictionary。这就是整个 System.Collections.Concurrent 命名空间的用途 要搜索的是“线程安全”。 @Servy 所以你是说如果他们知道你已经知道的,他们就不必问这个问题。然而他们还不知道这一点,所以在问。 (阅读两个定义并不总能帮助您了解区别) 【参考方案1】:

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

在没有多线程的情况下使用ConcurrentDictionary 的缺点是开销。所有允许它是线程安全的功能仍然存在,所有的锁和检查仍然会发生,需要处理时间并使用额外的内存。

【讨论】:

ConcurrentDictionary 使用单线程代码有什么缺点吗? @AaronFranke Overhead,允许线程安全的功能仍然存在,所涉及的检查仍然会发生,因此它将是一个更大的对象并且需要更多的处理才能使用。其他阅读代码的人也会去寻找线程并可能被它弄糊涂。【参考方案2】:

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

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

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

如果您只读取键或值,则 Dictionary 更快,因为如果字典没有被任何线程修改,则不需要同步。

【讨论】:

【参考方案3】:

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

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

    ConcurrentDictionary 提供的线程安全保证仅限于保护其内部状态。而已。如果您想做一些不重要的事情,例如将字典 另一个变量更新为原子操作,那么您就不走运了。这不是 ConcurrentDictionary 支持的方案。甚至不支持保护它包含的元素(如果它们是可变对象)。如果您尝试使用AddOrUpdate 方法更新其值之一,则该字典将受到保护,但该值不会。在这种情况下,Update 表示用另一个替换现有值,而不是修改现有值

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

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

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

【讨论】:

【参考方案4】:

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

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

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

例如,考虑使用普通Dictionary 类时可能看到的典型模式。

// 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 方法会为您处理这些问题,并将返回一个真/假,告诉您是否添加了它(或者该键是否已经在字典中)。

因此,除非您在代码的多线程部分中工作,否则您可能只使用标准的Dictionary 类。话虽如此,理论上您可以使用锁来防止对字典的并发访问; "Dictionary locking vs. ConcurrentDictionary" 已经解决了这个问题。

【讨论】:

感谢您的回答,它帮助了很多。即使它来自单个线程,在任何地方都使用 ConcurrentDictionary 会是一件坏事吗? 如果你在创建线程之前写(一个多线程,只做读),你可以使用非并发形式吗?我想这会更快,而且看不到它是如何崩溃的(直到你做了一些愚蠢的事情,比如写信给它)。 使用ConcurrentDictionary 可能没有问题,但我想至少会有一些开销取决于字典的使用方式可能会成为瓶颈。如果您想比较源以了解发生了什么,请查看ConcurrentDictionary.TryAddInternal() 和Dictionary.Insert() 的源。 我想这样做的主要目的是我不完全理解解决方案何时是多线程的,这是否特别意味着它的多线程,如果一个新线程正在编程创建和启动或在那里其他使应用程序成为多线程的东西?我看到他说异步任务,还有其他的吗? @Konrad 这真的取决于您的特定代码以及是否从多个线程访问字典(以可能产生竞争条件的方式)所以我能给出的最佳答案是“也许”:P 【参考方案5】:

ConcurrentDictionary 在您需要跨多个线程(即多线程)访问字典时很有用。 Vanilla Dictionary 对象不具备此功能,因此只能以单线程方式使用。

【讨论】:

是否会在任何地方使用 ConcurrentDictionary,即使它来自单个线程也是一件坏事? 不,因为如果您不打算对程序进行多线程处理,以后阅读您的代码的任何人都会感到困惑。此外,我预计任何并发数据结构都会有一些额外的开销,因为必须跨多个线程管理访问权限 你说不,然后继续当它是坏事,是坏事吗?我问的原因是因为它目前不能从主线程以外的其他线程访问,但将来可以。如果我决定实现多线程,是否值得只使用并发来处理所有方式或更改我的代码? 我不知道您的具体需求是什么。我只是在展示使用它们的利弊。如果您认为将来可能需要对代码进行多线程处理,并且您的字典可能会跨多个线程使用,那么使用 ConcurrentDictionary 将是一个有效的设计选择。但是,如果您不打算这样做,那么您应该避免这样做,因为该数据结构不会最适合您的要求

以上是关于什么时候应该使用 ConcurrentDictionary 和 Dictionary?的主要内容,如果未能解决你的问题,请参考以下文章

什么时候应该使用 Sql Azure,什么时候应该使用表存储?

什么时候应该使用 Microsoft.Owin 实现,什么时候应该使用 AspNetCore?

什么时候应该使用 JSF 组件,什么时候应该使用 html 标签? [关闭]

我们啥时候应该使用互斥锁,啥时候应该使用信号量

什么时候应该使用 var,什么时候应该使用 string、ulong、int 等 [重复]

什么时候应该使用标准的 html 标签/输入,什么时候应该使用 asp.net 控件?