线程安全的Dictionary.Add

dr.*_*vil 16 .net collections dictionary thread-safety

Dictionary.Add()线程安全的,当你只能插入?

我有一个从多线程插入密钥的代码,我是否还需要锁定Dictionary.Add()

添加新密钥时出现此异常:

Exception Source:    mscorlib
Exception Type: System.IndexOutOfRangeException
Exception Message:   Index was outside the bounds of the array.
Exception Target Site: Insert
Run Code Online (Sandbox Code Playgroud)

虽然这是非常罕见的.我知道这Dictionary不是线程安全的,虽然我认为只有调用.Add不会导致任何问题.

And*_*tan 25

字典根本不是线程安全,无论你是否只添加它 - 有一些内部结构需要保持同步(特别是当内部hashbuckets调整大小时).

你要么必须围绕它的任何操作实现自己的锁定,要么你在.Net 4.0中,你可以使用新的ConcurrentDictionary - 这绝对是太棒了 - 它完全是线程安全的.

另一种选择(更新)

也就是说 - 你可以使用另一种技术 - 但是根据你插入字典的数据类型以及你所有的密钥是否都是唯一的,我们需要进行一些调整:

为每个线程提供它自己插入的私有字典.

当每个线程完成时,将所有字典整理在一起并将它们合并为一个更大的字典; 你如何处理重复的密钥取决于你.例如,如果您通过键缓存项目列表,则可以将每个相同键列表合并为一个并将其放入主词典中.

官方回答:表现(你接受后)

因此,正如您的评论所说,您需要了解最佳方法(锁定或合并)以获得性能等.我无法告诉您这将是什么; 最终需要进行基准测试.我会看看我是否可以提供一些指导,但:)

首先 - 如果您知道您的Dictionar(y/ies)最终需要多少项,请使用(int)构造函数来最小化调整大小.

合并操作可能是最好的; 因为没有一个线程会相互干扰.除非两个对象共享相同密钥时涉及的过程特别冗长; 在这种情况下强制它在操作结束时在单个线程上发生可能最终通过并行化第一阶段将所有性能增益归零!

同样,由于您将有效地克隆字典,因此可能存在内存问题,因此如果最终结果足够大,您最终可能会消耗大量资源; 尽管如此 - 他们将被释放.

如果确实需要在密钥已经存在时做出线程级别的决策,那么您将需要一个lock(){}构造.

在字典中,这通常采用以下形状:

readonly object locker = new object();
Dictionary<string, IFoo> dictionary = new Dictionary<string, IFoo>();

void threadfunc()
{
  while(work_to_do)
  {
    //get the object outside the lock
    //be optimistic - expect to add; and handle the clash as a 
    //special case
    IFoo nextObj = GetNextObject(); //let's say that an IFoo has a .Name
    IFoo existing = null;
    lock(locker)
    {
      //TryGetValue is a god-send for this kind of stuff
      if(!dictionary.TryGetValue(nextObj.Name, out existing))
        dictionary[nextObject.Name] = nextObj;
      else
        MergeOperation(existing, nextObject);
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

现在,如果这MergeOperation真的慢; 然后你可以考虑释放锁,创建一个克隆对象,表示现有对象和新对象的合并,然后重新获取锁.但是 - 您需要一种可靠的方法来检查现有对象的状态在第一个锁和第二个锁之间没有变化(版本号对此有用).