只插入时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
虽然这种情况非常罕见。我知道Dictionary
不是线程安全的,尽管我认为只调用.Add
不会导致任何问题。
答案 0 :(得分: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);
}
}
}
现在,如果MergeOperation
真的那么慢;然后你可以考虑释放锁,创建一个克隆对象,表示现有对象和新对象的合并,然后重新获取锁。但是 - 您需要一种可靠的方法来检查现有对象的状态在第一个锁和第二个锁之间没有变化(版本号对此有用)。
答案 1 :(得分:3)
是的,这是插入元素时可以获得的异常,就像字典忙于增加存储桶数量一样。由另一个线程触发添加项目并且加载因子得到太高。字典对此特别敏感,因为重组需要一段时间。好的,让您的代码快速崩溃而不是每周一次。
查看线程中使用的每个代码行,并检查共享对象的使用位置。你还没有发现每周一次的崩溃。或者更糟糕的是,那些不会崩溃但只会偶尔产生错误数据的那些。