這是一篇翻譯,專門介紹Dictionary線程安全問題,原文網址如下
http://www.grumpydev.com/2010/02/25/thread-safe-dictionarytkeytvalue/
翻譯的不對之處,請指正。
介紹
一個寵物項目,我目前正在研究中需要使用內部字典來存儲“注冊”的數據,這是一個相當普遍的要求。 對於這個特殊的項目,.net 3.5中,我想至少嘗試使其“線程安全”的,
着眼於將其移動到ConcurrentDictionary,.NET4中保證線程不僅是安全的,而且具有更精細的鎖提高多線程性能。
一個再簡單不過的例子,卻有許多人犯着這樣的錯誤。
1.只是鎖定寫入?
很明顯,我們需要圍繞着寫同步原語(syncronisation primitive)類型的操作,但第一印象可能會使你認為讀應該沒問題-----尤其是如果我們堅持優先TryGetValue模式,而不是“如果它存在,那么得到的值”:
object myValue; // This is obviously not thread safe. // Something else can alter the collection // between ContainsKey and reading the // value. if (dictionary.ContainsKey("Testing")) { myValue = dictionary["Testing"]; } //Using TryGetValue looks safe though? //Doesn't it?! if (!dictionary.TryGetValue("Testing", out myValue)) throw new KeyNotFoundException();
不幸的是,如果使用Reflector查看器,看看TryGetValue是如何實現的,作為上面的第一種方法,很明顯它具有完全相同的並發問題:
public bool TryGetValue(TKey key, out TValue value) { int index = this.FindEntry(key); if (index >= 0) { value = this.entries[index].value; return true; } value = default(TValue); return false; }
2.因此,我將鎖定讀取和寫入?
下一個顯而易見的方法是要找到我們的代碼中無處不在的Dictionary,用於讀取或寫入,並使用同步原語(syncronisation primitive),例如鎖 ,以確保我們任何時候只在單個線程訪問它:
private readonly object padlock = new object(); private readonly Dictionary<string, object> dictionary = new Dictionary<string, object>(); private void Test() { object myValue; // Now we lock before we do anything lock (padlock) { if (dictionary.ContainsKey("Testing")) { myValue = dictionary["Testing"]; } } lock (padlock) { if (!dictionary.TryGetValue("Testing", out myValue)) { } } }
很簡單,但你依賴鎖定周圍的每一個訪問,這不僅難看,而且如果你錯過了一個,也可能容易出現錯誤。 因此,一旦把我們代碼遷移到.NET 4。在新的ConcurrentDictionary,我們將不得不通過代碼並依次取出每個鎖 – 這工作相當辛苦!
3.組合(組合設計模式的實現)
在這種方法中,總結了我們在自己的類中使用討厭地非線程安全的Dictionary,讓我使用想用的方法,並采取相應的任何鎖。 這個類只實現了“array” 的訪問和TryGetValue,但是這個方法足夠用了:
public class SafeDictionary<TKey, TValue> { private readonly object _Padlock = new object(); private readonly Dictionary<TKey, TValue> _Dictionary = new Dictionary<TKey, TValue>(); public TValue this[TKey key] { get { lock (_Padlock) { return _Dictionary[key]; } } set { lock (_Padlock) { _Dictionary[key] = value; } } } public bool TryGetValue(TKey key, out TValue value) { lock (_Padlock) { return _Dictionary.TryGetValue(key, out value); } } }
當我們防止任何直接進入Dictionar和每當我們需要訪問它並在內部使用我們的鎖的時候,我們現在可以使用代碼SafeDictionary無需擔心並發問題 - 無論是讀和寫操作!
4.到 .Net 4 ?
正如我前面提到的,.NET 4將支持數個“線程安全”的集合,其中包括Dictionary,在新的System.Collections.Concurrent命名空間 。 因此我們有自己實現的SafeDictionary,有以下幾個特點:
我們可以通過我們的代碼並替換所有引用SafeDictionary與ConcurrentDictionary。在我們的主代碼中沒有任何鎖,以至於我們可以這個直接替換。
我們可以改變我們的SafeDictionary內部使用一個ConcurrentDictionary,並刪除所有的內部鎖。
如果我們不介意使用額外的方法,我們可以刪除所有來自SafeDictionary實例和繼承ConcurrentDictionary:
public class SafeDictionary<Tkey, TValue> : ConcurrentDictionary<Tkey, TValue> { }
5.結論
相當長的博客文章提交一個相當簡單的問題,但有時候碰巧碰到簡單的並發可以是失誤和頭痛的根源。 一旦.NET 4中到達其並發集合 , 並行擴展和並行調試選項有望至少*這個頭痛的事情會自行消失。