锁定字典的TryGetValue() - 性能问题

时间:2011-05-04 14:39:58

标签: c# performance dictionary locking

我已经描述了我的应用程序并运行了一些性能测试,这使我相信以下if-lock-if安排:

private float GetValue(int id)
{
    float value;
    if (!dictionary.TryGetValue(id, out value))
    {
      lock (lockObj)
      {
        if (!dictionary.TryGetValue(id, out value))
        {
          value = ComputeValue(id);
          dictionary.Add(id, value);
        }
      }
    }
}

似乎比“lock-if”或使用ReaderWriterLockSlim执行得更快。但很少,我得到以下例外:

1) Exception Information
*********************************************
Exception Type: System.NullReferenceException
Message: Object reference not set to an instance of an object.
Data: System.Collections.ListDictionaryInternal
TargetSite: Int32 FindEntry(TKey)
HelpLink: NULL
Source: mscorlib

StackTrace Information
*********************************************
  at System.Collections.Generic.Dictionary`2.FindEntry(TKey key)
  at System.Collections.Generic.Dictionary`2.TryGetValue(TKey key, TValue& value)
  at MyNamespace.GetValue()
  .....
  .....

我在这里做错了什么?

编辑:澄清一下,这种方法的平均调用次数超过5000万次,冲突一般不到5000次。

由于

5 个答案:

答案 0 :(得分:15)

您在这里尝试做的事情根本不是支持的方案。 TryGetValue发生在锁之外,这意味着一个线程可以写入字典,而其他线程同时调用TryGetValueDictionary<TKey, TValue>固有支持的唯一线程方案是从多个线程读取。一旦你开始从多个线程读取和写入,所有的赌注都会被取消。

为了确保安全,您应该执行以下操作之一

  • Dictionary
  • 的所有读写访问使用单个锁
  • 使用专为多线程方案设计的ConcurrentDictionary<TKey, TValue>类型。

答案 1 :(得分:3)

您的代码对该集合的使用是线程安全的,在这种情况下您不需要锁定,或者它不是线程安全的,在这种情况下您总是需要锁定。

使用ConcurrentDictionary代替,这是线程安全的。

答案 2 :(得分:2)

Dictionary不是线程安全的。如果在执行TryGetValue时有任何内容正在添加到词典中,那么事情就会变得很糟糕。您对TryGetValue的第一次呼叫不受锁定保护。因此,如果线程A正在执行Add并且线程B进入第一个TryGetValue,则它可以抛出异常。

考虑使用System.Collections.Concurrent.ConcurrentDictionary。或者确保在每次访问时锁定字典。可能使用ReaderWriterLockSlim

答案 3 :(得分:0)

我认为删除不是线程安全的第一个if可以解决问题。

是否有理由两次执行相同的if语句?

答案 4 :(得分:0)

如果遇到异常,则可以使用try catch和try / fallback包装无锁的TryGetValue以进行锁定。在通常情况下,如果没有写操作,或者您很幸运并且在写操作过程中没有异常,则可以信任TryGetValue返回的值。