10

我想在我的应用程序中使用 ConcurrentDictionary,但首先我需要确保我正确理解它的工作原理。在我的应用程序中,我将有一个或多个线程写入字典或从中删除字典。而且,我将有一个或多个从字典中读取的线程。有可能同时发生。

我是否正确 ConcurrentDictionary 的实现会处理发生这种情况所需的所有锁定,并且我不需要提供自己的锁定?换句话说,如果一个线程正在写入字典或从中删除字典,则读取线程(或另一个写入线程)将被阻塞,直到更新或删除完成?

非常感谢。

4

2 回答 2

8

当前的实现使用了条带锁的混合(我昨天在https://stackoverflow.com/a/11950835/400547对某人的回答中建议的技术)并且非常非常认真地思考操作不可能导致的情况并发操作的问题或由并发操作引起的问题(其中有很多,但您必须非常确定是否使用它们)。

因此,如果您同时在并发字典上发生多个操作,则以下每种操作都是可能的:

  1. 甚至没有线程锁定,但一切正常。
  2. 一些线程锁,但它们锁在不同的东西上,没有锁争用。
  3. 一两个线程相互争用锁,速度变慢,但对性能的影响小于单个锁。
  4. 一个或两个线程需要将整个事物锁定一段时间(通常用于内部调整大小),这会阻塞在上述情况 3 中可能被阻塞的所有线程,尽管有些线程可以继续运行(那些读取的线程)。

这些都不涉及脏读,这只是与锁定模糊相关的问题(我自己的并发字典形式根本不使用锁,也没有脏读)。

此线程安全不适用于您的代码完成的批处理(如果您读取一个值然后写入一个值,则读取的值可能在您完成写入之前已更改),但请注意一些常见情况需要几个on 的调用Dictionary由单个方法处理ConcurrentDictionaryGetOrAddAddOrUpdate执行两个调用 a 的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);
}

然后你必须 lock on ConcurrentDictionary,虽然可能有一种方法可以将它与它提供的并发支持结合起来,但可能不会,所以只需使用Dictionarylock 即可。

否则,它归结为可能会有多少并发命中。如果您大多数情况下只有一个线程访问字典,但您需要防止并发访问的可能性,那么您绝对应该Dictionary使用锁。如果您将有六个或更多线程访问字典的时间段,那么您绝对应该选择ConcurrentDictionary(如果他们可能会访问相同数量的少量键,那么请查看我的版本,因为那是我表现更好的一种情况)。

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

编辑:要回答您给出的一位作者和一位读者的特定案例,根本不会有任何阻塞,因为这与多位读者和一位作者安全的大致相同的原因是安全的Hashtable,尽管ConcurrentDictionary超出了这在几个方面。

于 2012-08-15T14:23:47.840 回答
6

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

我不相信它会阻塞 - 它只是安全的。不会有任何损坏 - 您只会在读取是否看到写入方面进行竞赛。

来自关于并发集合的无锁性的常见问题解答

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

于 2012-08-15T12:54:58.730 回答