使用.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使用
keyId = 17
进入方法。 由于Dictionary是空的,if
的条件返回true
而线程1到达标记为A
的代码行。 - 线程1暂停,线程2进入方法,
keyId = 17
。 由于Dictionary是空的,if
的条件返回true
而线程2到达标记为A
的代码行。 - 线程2暂停,线程1恢复。 现在,线程1将
(17, 0)
到字典中。 - 线程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
可以同时支持多个读者,只要不修改集合即可。 即便如此,枚举通过集合本质上不是一个线程安全的过程。 在枚举与写访问争用的极少数情况下,必须在整个枚举期间锁定该集合。 要允许多个线程访问集合以进行读取和写入,您必须实现自己的同步。