.NET - 字典锁定与ConcurrentDictionary

The*_*eAJ 121 .net concurrency dictionary concurrentdictionary

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

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

我最近发现在.NET 4.0中有一组线程安全的集合,它似乎非常令人愉快.我想知道,什么是"更有效和更容易管理"的选项,因为我可以选择正常Dictionary的同步访问,或者具有ConcurrentDictionary已经线程安全的选项.

参考.NET 4.0 ConcurrentDictionary

ang*_*son 137

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

考虑一家没有店员的商店,结账时除外.如果人们不采取负责任的行动,你会遇到很多问题.例如,假设客户从金字塔罐中取出罐头,而店员正在建造金字塔,所有地狱都会破裂.或者,如果两个客户同时到达同一个项目,谁赢了怎么办?会有斗争吗?这是一个非线程安全的集合.有很多方法可以避免问题,但它们都需要某种锁定,或者说是某种方式的显式访问.

另一方面,考虑一个在办公桌上有店员的商店,你只能通过他购物.你排队,问他一个项目,他把它带回给你,然后你走出了界限.如果你需要多件物品,你可以在每次往返时尽可能多地拾取物品,但是你需要小心避免占用店员,这会激怒你身后的其他顾客.

现在考虑一下.在有一个职员的商店里,如果你一路走到前面,然后问职员"你有没有卫生纸",他说"是",然后你去"好吧,我"当我知道我需要多少时,我会回复你,"当你回到前线时,商店当然可以卖光了.线程安全集合不会阻止此方案.

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

非线程安全集合没有任何此类保证.例如,如果您在一个线程上向二叉树添加内容,而另一个线程忙于重新平衡树,则无法保证该项将被添加,或者即使该树在之后仍然有效,它可能已经超出了希望.

但是,线程安全集合不能保证线程上的顺序操作都在其内部数据结构的相同"快照"上工作,这意味着如果您有这样的代码:

if (tree.Count > 0)
    Debug.WriteLine(tree.First().ToString());
Run Code Online (Sandbox Code Playgroud)

你可能会得到一个NullReferenceException,因为inbetween tree.Counttree.First()另一个线程已经清除了树中剩余的节点,这意味着First()将返回null.

对于这种情况,您需要查看相关集合是否有安全的方式来获取您想要的内容,可能需要重写上面的代码,或者您可能需要锁定.

  • 这是对线程安全的一个很好的解释,但我不禁觉得这实际上没有解决OP的问题.OP询问的内容(以及我后来遇到的这个问题)是使用标准`Dictionary'和自己处理锁定与使用.NET 4+中内置的`ConcurrentDictionary`类型之间的区别.我实际上有点困惑,这被接受了. (80认同)
  • 启用线程的应用程序中的主要问题是可变数据结构.如果你可以避免这种情况,那么你可以省去很多麻烦. (19认同)
  • 每当我读到这篇文章时,即使这一切都是真的,我们也会走错路. (8认同)
  • 线程安全不仅仅是使用正确的集合.使用正确的集合是一个开始,而不是您必须处理的唯一事情.我想这就是OP想知道的.我无法猜到为什么这被接受了,自从我写这篇文章以来已经有一段时间了. (4认同)
  • 我认为@ LasseV.Karlsen试图说的是......线程安全很容易实现,但是你必须在如何处理这种安全性方面提供一定程度的并发性.问问自己,"我可以在保持对象线程安全的同时进行更多操作吗?" 考虑这个例子:两个客户想要返回不同的项目.当您作为经理去旅行补货时,您是否可以在一次行程中重新进货并同时处理客户的要求,或者您是否每次客户退货时都要去旅行? (2认同)

Mar*_*ers 61

使用线程安全集合时仍需要非常小心,因为线程安全并不意味着您可以忽略所有线程问题.当集合将自身通告为线程安全时,通常意味着即使多个线程同时读取和写入,它仍保持一致状态.但这并不意味着如果单个线程调用多个方法,它将看到"逻辑"结果序列.

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

所以要使用它们,但不要认为它给你一个免费的通行证来忽略所有的并发问题.你还需要小心.

  • 这并不意味着在这里听起来很简单,但`TryGetValue`一直是`Dictionary'的一部分,并且一直是推荐的方法."ConcurrentDictionary"引入的重要"并发"方法是"AddOrUpdate"和"GetOrAdd".所以,很好的答案,但可以选择一个更好的例子. (11认同)
  • 所以基本上你说如果你没有正确使用对象它将无法正常工作? (4认同)
  • 对 - 与具有事务的数据库不同,大多数并发内存中搜索结构错过了*isolation*的概念,它们只保证内部数据结构正确. (4认同)
  • @Bruno:不.如果你还没有锁定,另一个线程可能已经删除了两个调用之间的密钥. (3认同)
  • 基本上,您需要使用TryAdd而不是常用的Contains-> Add。 (2认同)

Ste*_*nev 37

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

我会说,只需使用ConcurrentDictionary.


Kon*_*tin 15

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

  • *GetOrAdd*具有重载*GetOrAdd(TKey,Func <TKey,TValue>)*,因此只有在字典中不存在键的情况下,第二个参数才可以进行延迟初始化.除了*TryGetValue*用于获取数据,而不是修改.对这些方法中的每一个的后续调用可能导致另一个线程可能在两个调用之间添加或删除了密钥. (6认同)

sco*_*eep 13

你见过.Net 3.5sp1 的Reactive Extensions吗?根据Jon Skeet的说法,他们已经为.Net3.5 sp1反向移植了一系列并行扩展和并发数据结构.

.Net 4 Beta 2有一组示例,详细描述了如何使用并行扩展.

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

编辑:.NET 4 ConcurrentDictionary和模式.

微软发布了一个名为Patterns of Paralell Programming的pdf.它非常值得下载,因为它描述了非常好的细节,正确的模式用于.Net 4并发扩展和反模式,以避免.这里是.


Cha*_*ion 5

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