【问题标题】:ConcurrentDictionary<TKey,TValue> vs Dictionary<TKey,TValue>ConcurrentDictionary<TKey,TValue> 与 Dictionary<TKey,TValue>
【发布时间】:2011-03-14 19:32:22
【问题描述】:

作为MSDN says

ConcurrentDictionary&lt;TKey, TValue&gt; Class 表示一个线程安全的键值对集合,可以被多个线程同时访问。

但据我所知,System.Collections.Concurrent 类是为 PLINQ 设计的。

我有Dictionary&lt;Key,Value&gt;,它将在线客户端保存在服务器中,当我可以访问它时,我通过锁定对象使其成为线程安全的。

在我的情况下,我可以安全地将Dictionary&lt;TKey,TValue&gt; 替换为ConcurrentDictionary&lt;TKey,TValue&gt; 吗?更换后性能会提升吗?

Here 在第 5 部分 Joseph Albahari 提到它是为并行编程设计的

  • 并发集合针对并行编程进行了调整。除了高度并发的场景外,传统集合的性能都优于它们。
  • 线程安全的集合并不能保证使用它的代码是线程安全的。
  • 如果在另一个线程正在修改并发集合时枚举并发集合,则不会引发异常。相反,您会得到新旧内容的混合。
  • 没有并发版本的 List。
  • 并发堆栈、队列和包类在内部使用链表实现。这使得它们的内存效率低于非并发 Stack 和 Queue 类,但更适合并发访问,因为链表有利于无锁或低锁实现。 (这是因为将节点插入到链表中只需要更新几个引用,而将元素插入到类似列表的结构中可能需要移动数千个现有元素。)

【问题讨论】:

  • 我不知道 System.Collections.Concurrent 类是“为 PLINQ 设计的”这一事实 - 你从哪里得到这个想法?
  • @BrokenGlass:也许从某种意义上说,lambda 和匿名类型是“为 LINQ 设计的”;它们出现在框架中因为 LINQ,但它们肯定具有超越它的适用性。
  • @BrokenGlass 和 @Adam Robinson,查看编辑后的帖子
  • 这仅表明它已针对包括 PLINQ 的 并行编程 进行了调整,但通常也适用于任何多线程场景,从 TPL 到手动生成的方法有很多线程。
  • 好的@BrokenGlass,我认为有相同的......谢谢!

标签: c# c#-4.0 dictionary concurrent-collections


【解决方案1】:

如果不知道更多关于你在锁内做什么,那么就不可能说出来。

例如,如果您的所有字典访问都如下所示:

lock(lockObject)
{
    foo = dict[key];
}

... // elsewhere

lock(lockObject)
{
    dict[key] = foo;
}

然后你就可以把它换掉(虽然你可能不会看到性能上有任何差异,所以如果它没有坏,就不要修复它)。但是,如果您在与字典交互的锁定块中做任何花哨的事情,那么您必须确保字典提供一个 single 函数来完成您正在做的事情在 lock 块中,否则您最终会得到功能上与以前不同的代码。要记住的最重要的事情是字典只保证对字典的并发调用以串行方式执行;它无法处理您在 您的 代码中与字典多次交互的单个操作的情况。如果ConcurrentDictionary 未考虑此类情况,则需要您自己的并发控制。

值得庆幸的是,ConcurrentDictionary 为更常见的多步操作(如AddOrUpdateGetOrAdd)提供了一些辅助函数,但它们不能涵盖所有情况。如果您发现自己不得不将自己的逻辑硬塞到这些函数中,那么处理自己的并发可能会更好。

【讨论】:

    【解决方案2】:

    这并不像将Dictionary 替换为ConcurrentDictionary 那样简单,您需要调整您的代码,因为这些类具有行为不同的新方法,以保证线程安全。

    例如,不是调用AddRemove,而是调用TryAddTryRemove。使用这些原子行为的方法很重要,就好像您进行两次调用,其中第二次调用取决于第一次的结果,您仍然会遇到竞争条件并且需要lock

    【讨论】:

      【解决方案3】:

      您可以将Dictionary&lt;TKey, TValue&gt; 替换为ConcurrentDictionary&lt;TKey, TValue&gt;

      虽然对性能的影响可能不是您想要的(如果有很多锁定/同步,性能可能会受到影响……但至少您的集合是线程安全的)。

      【讨论】:

        【解决方案4】:

        虽然我不确定替换困难,但如果您有任何地方需要在同一个“锁定会话”中访问字典中的多个元素,那么您需要修改您的代码。

        如果 Microsoft 为读取和写入提供单独的锁,它可以提高性能,因为读取操作不应阻塞其他读取操作。

        【讨论】:

          【解决方案5】:

          是的,您可以安全地替换,但是为 plinq 设计的字典可能有一些额外的代码来添加您可能不会使用的功能。但是性能开销将非常小。

          【讨论】: