141

我找不到关于ConcurrentDictionary类型的足够信息,所以我想我会在这里问一下。

目前,我使用 aDictionary来保存由多个线程(来自线程池,因此没有确切数量的线程)不断访问的所有用户,并且它具有同步访问。

最近发现 .NET 4.0 中有一组线程安全的集合,看起来很讨人喜欢。我想知道,“更高效、更容易管理”的选项是什么,因为我可以选择拥有一个正常Dictionary的同步访问,或者拥有一个ConcurrentDictionary已经是线程安全的。

参考 .NET 4.0ConcurrentDictionary

4

7 回答 7

160

可以以不同的方式看待线程安全集合与非线程安全集合。

考虑一家没有店员的商店,结账时除外。如果人们不负责任地行事,就会有很多问题。例如,假设客户从金字塔罐中取出一个罐头,而店员正在建造金字塔,那么一切都会崩溃。或者,如果两个顾客同时拿到同一个商品,谁会赢?会打架吗?这是一个非线程安全的集合。有很多方法可以避免问题,但它们都需要某种锁定,或者以某种方式显式访问。

另一方面,考虑一家在办公桌前有店员的商店,您只能通过他购物。你排队,向他要一件东西,他把它拿回来给你,你就离开了。如果你需要多件物品,每次往返只能取你记得的尽可能多的物品,但你需要小心,不要挤到店员身上,这会激怒你后面排队的其他顾客。

现在考虑一下。在有一个店员的商店里,如果你一直排到最前面,问店员“你有卫生纸吗”,他说“有”,然后你去“好吧,我”当我知道我需要多少时会回复你”,然后当你回到队伍的最前面时,商店当然可以卖光了。线程安全集合不会阻止这种情况。

线程安全集合保证其内部数据结构始终有效,即使从多个线程访问也是如此。

非线程安全的集合没有任何此类保证。例如,如果您在一个线程上向二叉树添加一些东西,而另一个线程正忙于重新平衡树,则无法保证该项目将被添加,或者即使该树之后仍然有效,它可能已损坏到无法预料的程度。

然而,线程安全集合并不能保证线程上的顺序操作都在其内部数据结构的同一个“快照”上工作,这意味着如果你有这样的代码:

if (tree.Count > 0)
    Debug.WriteLine(tree.First().ToString());

您可能会收到 NullReferenceException ,因为在tree.Count和之间tree.First(),另一个线程已经清除了树中的剩余节点,这意味着First()将返回null

对于这种情况,您要么需要查看有问题的集合是否有一种安全的方式来获取您想要的东西,也许您需要重写上面的代码,或者您可能需要锁定。

于 2009-12-27T17:23:37.210 回答
74

使用线程安全集合时仍然需要非常小心,因为线程安全并不意味着您可以忽略所有线程问题。当一个集合宣称自己是线程安全的时,通常意味着即使多个线程同时读取和写入,它也会保持一致的状态。但这并不意味着如果单个线程调用多个方法,它就会看到“逻辑”的结果序列。

例如,如果您首先检查一个键是否存在,然后获取与该键对应的值,那么即使使用 ConcurrentDictionary 版本,该键也可能不再存在(因为另一个线程可能已经删除了该键)。在这种情况下,您仍然需要使用锁定(或者更好:使用TryGetValue组合两个调用)。

所以一定要使用它们,但不要认为它可以让你免费忽略所有并发问题。你仍然需要小心。

于 2009-12-22T21:10:55.397 回答
45

内部 ConcurrentDictionary 为每个哈希桶使用单独的锁。只要你只使用 Add/TryGetValue 和类似的方法来处理单个条目,字典将作为一个几乎无锁的数据结构工作,并具有各自的甜蜜性能优势。OTOH 枚举方法(包括 Count 属性)一次锁定所有存储桶,因此在性能方面比同步字典差。

我会说,只需使用 ConcurrentDictionary。

于 2010-12-26T10:17:19.290 回答
18

我认为 ConcurrentDictionary.GetOrAdd 方法正是大多数多线程场景所需要的。

于 2009-12-27T16:36:53.387 回答
14

您是否看过.Net 3.5sp1 的响应式扩展。根据 Jon Skeet 的说法,他们已经为 .Net3.5 sp1 向后移植了一组并行扩展和并发数据结构。

.Net 4 Beta 2 有一组示例,其中非常详细地描述了如何使用它们的并行扩展。

我刚刚在上周使用 32 个线程来测试 ConcurrentDictionary 来执行 I/O。它似乎像宣传的那样工作,这表明已经进行了大量的测试。

编辑:.NET 4 ConcurrentDictionary 和模式。

Microsoft 发布了一个名为 Patterns of Parallel Programming 的 pdf。它非常值得下载,因为它非常详细地描述了用于 .Net 4 并发扩展的正确模式以及要避免的反模式。这里是。

于 2010-01-05T16:51:54.007 回答
5

基本上你想使用新的 ConcurrentDictionary。开箱即用,您必须编写更少的代码来制作线程安全的程序。

于 2009-12-22T21:10:43.977 回答
-1

我们将 ConcurrentDictionary 用于缓存集合,每 1 小时重新填充一次,然后由多个客户端线程读取,类似于示例线程安全吗?问题。

我们发现,将其更改为 ReadOnlyDictionary可以 提高整体性能。

于 2013-05-05T04:06:11.097 回答