无论是仅添加还是其他操作,字典(Dictionary)都不是线程安全的,因为它有一些内部结构需要保持同步,特别是当内部哈希桶得到重新调整大小时。
你要么在任何对其进行的操作周围实现自己的锁定,要么如果你在 .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)
{
IFoo nextObj = GetNextObject();
IFoo existing = null;
lock(locker)
{
if(!dictionary.TryGetValue(nextObj.Name, out existing))
dictionary[nextObject.Name] = nextObj;
else
MergeOperation(existing, nextObject);
}
}
}
如果MergeOperation
非常慢,您可以考虑释放锁定,创建一个克隆对象,表示现有对象和新对象的合并,然后重新获取锁定。但是,您需要一种可靠的方法来检查在第一次锁定和第二次锁定之间现有对象的状态没有发生变化(版本号对此很有用)。
new object()
作为标记),看看效果如何。如果对性能满意,请坚持使用它 :) - Andras Zoltan