ConcurrentDictionary<TKey, TValue>与Dictionary<TKey, TValue>的区别

29
根据MSDN文档,ConcurrentDictionary类表示一组线程安全的键值对集合,可同时被多个线程访问。但我知道System.Collections.Concurrent类是为PLINQ设计的。 我有一个在服务器中保存在线客户端的Dictionary,并通过锁定对象使其线程安全。在我的情况下,我可以安全地用ConcurrentDictionary替换Dictionary吗?替换后性能会提高吗?在第5部分中,Joseph Albahari在这里提到,它是为并行编程而设计的。并发集合针对并行编程进行了调整,在除高度并发的情况外,传统集合的性能优于它们。线程安全的集合不能保证使用它的代码是线程安全的。如果您在另一个线程修改并发集合时枚举它,不会抛出异常。相反,您会得到旧内容和新内容的混合。没有List的并发版本。并发堆栈、队列和包类在内部使用链表实现。这使它们的内存效率低于非并发堆栈和队列类,但对于并发访问来说更好,因为链表有助于无锁或低锁定实现。(因为将节点插入到链表中只需要更新几个引用,而将元素插入到类似于List的结构中可能需要移动数千个现有元素。)

1
我不知道System.Collections.Concurrent类是“为PLINQ而设计”的事实 - 你从哪得到这个想法的? - BrokenGlass
@BrokenGlass:或许从某种意义上来说,Lambda 和匿名类型是“为 LINQ 设计的”;它们出现在框架中是由于 LINQ,但它们绝对不仅限于此。 - Adam Robinson
@BrokenGlass和@Adam Robinson,请查看编辑后的帖子。 - Arsen Mkrtchyan
这仅仅说明它是为并行编程进行了调整,其中包括PLINQ,但也适用于任何多线程场景。从TPL到手动生成的线程,有许多方法可供选择。 - BrokenGlass
好的 @BrokenGlass,我以为它们是一样的... 谢谢! - Arsen Mkrtchyan
5个回答

19

如果不了解您在锁内执行的操作,那么很难确定。

例如,如果您所有的字典访问都是这样的:

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

... // elsewhere

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

如果仅仅是替换字典类型,那问题不大(但你可能看不到性能方面的改进,所以如果没有问题就不要去修复它)。

然而,如果你在锁定块中进行了任何复杂操作,同时又涉及到字典,那么你必须确保字典提供了一个单一的函数来完成你在锁定块内部要做的事情,否则你最终得到的代码将与之前的功能不同。需要记住的最重要的一点是,字典只保证对字典的并发调用以串行方式执行;它无法处理你的代码中有一个单一操作多次与字典交互的情况。这种情况如果没有被ConcurrentDictionary考虑到,就需要自己进行并发控制。

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


5
这并不像简单地用ConcurrentDictionary替换Dictionary那样简单,你需要调整代码,因为这些类有新方法,它们的行为有所不同,以保证线程安全。
例如,你需要使用TryAddTryRemove而不是调用AddRemove。重要的是使用这些原子性方法,如果你进行两次调用,第二次依赖于第一次的结果,你仍然会有竞态条件,并且需要一个lock

1

您可以使用ConcurrentDictionary<TKey, TValue>替换Dictionary<TKey, TValue>

然而,对性能的影响可能不是您想要的(如果存在大量的锁定/同步操作,则性能可能会受到影响...但至少您的集合是线程安全的)。


1

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

如果Microsoft为读取和写入提供了单独的锁定,那么它可能会提供更好的性能,因为读取操作不应阻止其他读取操作。


0

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接