ConcurrentDictionary Object - 通过不同的线程读取和写入

时间:2012-08-15 12:50:12

标签: c# multithreading thread-safety

我想在我的应用程序中使用ConcurrentDictionary,但首先我需要确保我正确理解它是如何工作的。在我的应用程序中,我将有一个或多个线程写入或删除字典。而且,我将有一个或多个从字典中读取的线程。潜在地,所有这些都在同一时间。

我是否正确,ConcurrentDictionary的实现负责处理所有必需的锁定,并且我不需要提供自己的锁定?换句话说,如果一个线程正在写入或删除字典,那么读取线程(或另一个写入线程)将被阻止,直到更新或删除完成?

非常感谢。

2 个答案:

答案 0 :(得分:7)

目前的实施方案使用条纹锁的混合物(我在昨天https://stackoverflow.com/a/11950835/400547回答某人的建议中提出的技巧)并且认为非常非常努力关于操作无法解决的情况并发操作可能导致问题或导致问题(这些操作很多,但是如果你使用它们,你必须非常确定)。

因此,如果您同时在并发字典上进行了多项操作,则可能出现以下情况:

  1. 没有线程甚至锁定,但一切都正常。
  2. 某些线程锁定,但它们锁定了不同的东西,并且没有锁争用。
  3. 一个或两个线程彼此之间存在锁争用,并且速度变慢,但对性能的影响小于单锁时的影响。
  4. 一个或两个线程需要锁定整个事物一段时间(通常用于内部调整大小),这会阻止上面案例3中可能被阻塞的所有线程,尽管有些线程可以继续(那些读取)。
  5. 这些都不涉及脏读,这只是与锁定有关的问题(我自己的并发字典形式根本不使用锁,也没有脏读)。

    此线程安全不适用于您的代码完成的批处理(如果您读取值然后写入值,则读取的值可能在您完成写入之前已更改),但请注意一些常见情况,要求Dictionary上的一些电话可以通过ConcurrentDictionary上的单一方法(GetOrAddAddOrUpdate进行,可以使用Dictionary进行两次调用它们可以原子方式完成 - 但请注意,某些重载中涉及的Func可能会被多次调用。

    由于这个原因,ConcurrentDictionary没有增加的危险,所以你应该选择如下:

    如果您不得不锁定一些与ConcurrentDictionary提供的操作不匹配的操作,例如:

    lock(lockObj)
    {
      var test = dict[key1];
      var test2 = dict[key2];
      if(test < test2 && test2 < dict[key3] && SomeOtherBooleanProducer())
        dict[key4] = SomeFactoryCall(key4);
    }
    

    然后你必须锁定ConcurrentDictionary,虽然可能有一种方法可以将它与支持并发的方式结合起来,但可能不会,所以只需使用{{1带锁。

    否则,归结为可能会有多少并发命中。如果你大多只有一个线程打到字典,但你需要防止并发访问的可能性,那么你一定要带锁来Dictionary。如果你打算有半打或更多线程进入字典,那么你一定要选择Dictionary(如果他们可能会使用相同数量的小键,那就去看看吧在我的版本,因为这是我有更好的表现的一种情况。)

    就在“少数”和“多”线程之间的中间位置,很难说。我会说,如果定期有两个以上的线程,那么请使用ConcurrentDictionary。如果不出意外,并发性的要求往往会在项目的整个生命周期中增加,而不是减少。

    编辑:要回答你给出的一个作家和一个读者的特定情况,根本就没有任何阻止,因为这是安全的,原因大致相同,因为多个读者和一个作者在{{ {1}}虽然ConcurrentDictionary在几个方面超出了这个范围。

答案 1 :(得分:5)

  

换句话说,如果一个线程正在写入或删除字典,那么读取线程(或另一个写入线程)将被阻止,直到更新或删除完成?

相信它会阻止 - 它只是安全。不会有任何腐败 - 你只会在阅读是否看到写作方面有竞争。

来自FAQ about the lock-free-ness of the concurrent collections

  

ConcurrentDictionary<TKey,TValue>在添加或更新字典中的数据时使用细粒度锁定,但它对于读取操作完全没有锁定。通过这种方式,它针对从字典中读取最频繁操作的场景进行了优化。