使用.Net中的Dictionary 进行线程安全

我有这个function:

static Dictionary KeyValueDictionary = new Dictionary(); static void IncreaseValue(int keyId, int adjustment) { if (!KeyValueDictionary.ContainsKey(keyId)) { KeyValueDictionary.Add(keyId, 0); } KeyValueDictionary[keyId] += adjustment; } 

我原本认为这不是线程安全的。 但是,到目前为止,在测试它时,我没有看到同时从多个线程调用它时的任何exception。

我的问题:它是线程安全还是我到目前为止幸运? 如果它是线程安全的那么为什么?

但是,到目前为止,在测试它时,我没有看到同时从多个线程调用它时的任何exception。

它是线程安全还是我到目前为止幸运? 如果它是线程安全的那么为什么?

你很幸运。 这些带有线程的bug很容易制作,因为测试可以给你一种错误的安全感,你可以正确地做事。

事实certificate,当你有多个编写器时, Dictionary不是线程安全的。 文档明确指出:

Dictionary可以同时支持多个读者,只要不修改集合即可。 即便如此,枚举通过集合本质上不是一个线程安全的过程。 在枚举与写访问争用的极少数情况下,必须在整个枚举期间锁定该集合。 要允许多个线程访问集合以进行读取和写入,您必须实现自己的同步。

或者,使用ConcurrentDictionary 。 但是,您仍然必须编写正确的代码(请参阅下面的注释)。

除了你很幸运避免使用Dictionary缺乏线程安全性之外,你的代码还存在危险的缺陷。 以下是您如何获得代码错误的方法:

 static void IncreaseValue(int keyId, int adjustment) { if (!KeyValueDictionary.ContainsKey(keyId)) { // A KeyValueDictionary.Add(keyId, 0); } KeyValueDictionary[keyId] += adjustment; } 
  1. 字典是空的。
  2. 线程1使用keyId = 17进入方法。 由于Dictionary是空的, if的条件返回true而线程1到达标记为A的代码行。
  3. 线程1暂停,线程2进入方法, keyId = 17 。 由于Dictionary是空的, if的条件返回true而线程2到达标记为A的代码行。
  4. 线程2暂停,线程1恢复。 现在,线程1将(17, 0)到字典中。
  5. 线程1暂停,现在线程2恢复。 现在,线程2尝试将(17, 0)添加到字典中。 由于密钥违规而抛出exception。

还有其他可能发生exception的方案。 例如,线程1可以在加载KeyValueDictionary[keyId]的值时暂停(假设它加载keyId = 17 ,并获得值42 ),线程2可以进入并修改该值(假设它加载keyId = 17 ,添加调整27 ),现在线程1恢复并将其调整添加到它加载的值(特别是,它没有看到线程2对与keyId = 17相关联的值的修改!)。

请注意,即使使用ConcurrentDictionary也可能导致上述错误! 您的代码不安全,原因与Dictionary的线程安全性或缺乏线程安全性无关

要使用并发字典使代码具有线程安全性,您必须说:

 KeyValueDictionary.AddOrUpdate(keyId, adjustment, (key, value) => value + adjustment); 

这里我们使用ConcurrentDictionary.AddOrUpdate

它不是线程安全的,但不检查,因此可能没有注意到静默腐败。

它似乎在很长一段时间内都是线程安全的,因为只有当它需要rehash()时才会有exception的机会。 否则,它只会破坏数据。

.NET库有一个线程安全字典, ConcurrentDictionary http://msdn.microsoft.com/en-us/library/dd287191.aspx

更新:我没有完全回答这个问题,所以这里更新了更多的答案,提出了确切的问题。 根据MSDN: http : //msdn.microsoft.com/en-us/library/xfhwa508.aspx

只要不修改集合,Dictionary就可以同时支持多个读者。 即便如此,枚举通过集合本质上不是一个线程安全的过程。 在枚举与写访问争用的极少数情况下,必须在整个枚举期间锁定该集合。 要允许多个线程访问集合以进行读取和写入,您必须实现自己的同步。

有关线程安全的替代方法,请参阅ConcurrentDictionary。

此类型的公共静态(在Visual Basic中为Shared)成员是线程安全的。

到目前为止,你刚刚幸运。 它不是线程安全的。

来自Dictionary文档 ……

Dictionary可以同时支持多个读者,只要不修改集合即可。 即便如此,枚举通过集合本质上不是一个线程安全的过程。 在枚举与写访问争用的极少数情况下,必须在整个枚举期间锁定该集合。 要允许多个线程访问集合以进行读取和写入,您必须实现自己的同步。