【问题标题】:is locking necessary for Dictionary lookup?字典查找需要锁定吗?
【发布时间】:2010-10-22 19:38:46
【问题描述】:
lock(dictionaryX)
{
   dictionaryX.TryGetValue(key, out value);
}

在查找字典时是否需要锁定?

该程序是多线程的,同时向 dict 添加键/值。 dict 被锁定。

【问题讨论】:

  • 如果您的问题很严重并且涉及多线程程序,请确保您在问题中明确提及。
  • 我的问题没有你的评论那么严重。

标签: c# multithreading dictionary locking lookup


【解决方案1】:

如上所述here

在没有锁定的情况下使用 TryGetValue() 是不安全的。字典暂时处于不适合阅读的状态,而另一个线程正在编写字典。字典会随着它包含的条目数量的增加不时地重新组织自己。当您在此重组发生的确切时间阅读时,当存储桶已更新但尚未更新值条目时,您将冒着为键找到错误值的风险。

更新: 也可以看看this page 的“线程安全”部分。

【讨论】:

  • 这仅在涉及线程时才成立。没有线程就不需要锁。
  • OP 明确提到这个操作是在多线程环境中进行的。
  • 我同意。但是问题作者的意思是在多线程环境中使用锁。
  • 嗯,我没有遇到过这种情况。但是根据 JaredPar 的说法,除非您处于多线程环境中,否则不需要锁定。看看他的声望点,我敢肯定他知道他在说什么!
  • 嗯,答案的副本获得了比我发布时更多的支持。不确定这里是否需要署名,但应该是。
【解决方案2】:

与编程中的许多微妙问题一样,答案是:不一定。

如果只是添加值作为初始化,那么后续的读取就不需要同步了。但是,另一方面,如果您要一直阅读和写作,那么您绝对需要保护该资源。

但是,成熟的lock 可能不是最好的方法,具体取决于您的词典获得的流量。如果您使用的是 .NET 3.5 或更高版本,请尝试ReaderWriterLockSlim

【讨论】:

  • +1:虽然值得一试,但在这种情况下,ReaderWriterLockSlim 很可能会比普通的旧 lock 慢...很多事实上更慢。在我自己的个人测试中,我发现 ReaderWriterLockSlim 的开销大约是 5 倍,而旧的 ReaderWriterLock 大约是 15 倍。 RW 锁的应用范围比普通的lock 窄得多,因此在大多数情况下它会更慢。当锁被持有很长时间并且写入者的数量远远超过读取者的数量时,RW 锁真的会发光。不过值得进行基准测试。
【解决方案3】:

如果您有多个线程访问字典,那么您确实需要锁定更新和查找。您需要锁定查找的原因是在您进行查找的同时可能会发生更新,并且字典可能在更新期间处于不一致状态。例如,假设您有一个线程在执行此操作:

if (myDictionary.TryGetValue(key, out value))
{
}

一个单独的线程正在这样做:

myDictionary.Remove(key);

可能发生的情况是执行TryGetValue 的线程确定该项目在字典中,但在它可以检索该项目之前,另一个线程将其删除。结果是执行查找的线程要么抛出异常,要么TryGetValue 将返回true,但value 将是null,或者可能是与键不匹配的对象。

这只是可能发生的一件事。如果您在一个线程上进行查找,而另一个线程添加了您尝试查找的值,则可能会发生类似的灾难性事件。

【讨论】:

    【解决方案4】:

    仅当您在线程之间同步对资源的访问时才需要锁定。只要不涉及多线程,这里就不需要锁定。

    在从多个线程更新和读取值的上下文中,是的,锁是绝对必要的。事实上,如果您使用的是 4.0,您应该考虑切换到专为并发访问设计的集合之一。

    【讨论】:

    • @user177883 除非涉及多个线程,否则对Dictionary 的任何操作都不需要锁定
    • 我还提到了在向其中添加数据时字典被锁定。但是为什么我会在查找时再次锁定它?这是否意味着,当 dict 被锁定以进行写入时,我仍然可以进行查找?
    • @user177883 因为如果您在查找过程中也没有锁定,那么您可以在写入过程中开始查找,因此看到字典处于中间状态。
    • "锁确保一个线程不进入临界区而另一个线程处于代码的临界区。如果另一个线程试图进入锁定的代码,它会等待(阻塞)直到对象被释放。”如果我在写作时锁定它,我怀疑我是否需要在查找时锁定它??
    • @user177883:锁定需要读者和作者的合作。如果读者没有锁定,那么就好像作者根本没有进行任何锁定一样。 “锁定”仅意味着获取互斥锁的所有权,它不会停止执行不尝试获取互斥锁的代码。
    【解决方案5】:

    使用new ConcurrentDictionary<TKey, TValue> object,您就可以忘记必须进行任何锁定。

    【讨论】:

    • 你不能忘记锁。如果你在 ConcurrentDictionary 上这样做:if (concDic.Contains("key"))concDic["key"].Do(); 你会得到一个异常,因为这个键可以在 if 和 Do() 之间删除。
    【解决方案6】:

    是的,您需要锁定字典才能在多线程环境中访问。对字典的写入不是原子的,因此它可以添加键,但不能添加值。在这种情况下,当您访问它时,您可能会遇到异常。

    【讨论】:

    • @user177 是的,我说的是阅读。当我提到写作时,这是您尝试阅读期间可能发生的事情的一个示例。
    【解决方案7】:

    如果您使用的是 .Net 4,则可以替换为 ConcurrentDictionary 以安全地执行此操作。在System.Collection.Concurrent namespace 中还有其他类似的集合,当您需要多线程访问时首选。

    如果您可以选择,请不要使用自己滚动锁定。

    【讨论】:

      【解决方案8】:

      是的,如果此字典是多个线程之间的共享资源,您应该锁定。这可确保您获得正确的值,并且其他线程不会在您的 Lookup 调用中途更改值。

      【讨论】:

        【解决方案9】:

        是的,如果您对该字典进行多线程更新,则必须锁定。详情请查看这篇精彩的帖子:“Thread safe” Dictionary(TKey,TValue)

        但是自从引入了ConcurrentDictionary<>,您可以通过.NET 4 或在3.5 中使用Rx 来使用它(它包含System.Threading.dll,实现了新的线程安全集合)

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2014-07-19
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多