【问题标题】:ConcurrentDictionary Object - Reading and writing via different threadsConcurrentDictionary Object - 通过不同的线程读写
【发布时间】:2012-08-15 12:50:11
【问题描述】:

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

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

非常感谢。

【问题讨论】:

  • ConcurrentDictionary 的文档对这个主题有什么看法?我想对象的摘要会讨论它是否是线程安全的,以及它的安全程度。

标签: c# multithreading thread-safety


【解决方案1】:

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

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

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

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

此线程安全不适用于您的代码完成的批处理(如果您读取一个值然后写入一个值,则读取的值可能在您完成写入之前已更改),但请注意一些常见情况需要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,虽然可能有一种方法可以将它与它提供的并发支持相结合,但可能不会,所以只需使用Dictionary锁。

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

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

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

【讨论】:

    【解决方案2】:

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

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

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

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

    【讨论】:

    • 谢谢。听起来可能会有“脏读”(使用 SQL 术语)。对于我的应用程序,这没关系。我想如果我想完全避免这种可能性,我不会使用 ConcurrentDictionary,我会使用非并发的并实现我自己的锁定。
    • @RandyMinder:不,我不认为脏读是可能的——即使锁定了你仍然会有数据竞争,不是吗?读取发生在写入之前,或者发生在写入之后。所有无锁意味着在读取之前开始的写入仍然无法在读取之前看到。我相信,在读取开始之前结束的写入应该始终可见。
    • 我可以很容易地对此进行测试以找出答案。但是,就我的目的而言,这不是问题。 ConcurrentDictionary 的实现方式对我有用。您是否计划明年(在俄亥俄州桑达斯基)成为 CodeMash 2013?
    • @RandyMinder:希望是,是的。我们会看到:)
    • @RandyMinder ConcurrentDictionary 仅保证 操作是安全的。您可以以对您的代码不是线程安全的方式使用ConcurrentDictionary。例如foreach(var pair in concurrentDictionary) DoSomethingWithPair(pair); 在您的应用中可能是线程安全的,也可能不是线程安全的。
    猜你喜欢
    • 2021-02-10
    • 1970-01-01
    • 2018-04-12
    • 1970-01-01
    • 2020-10-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多