“線程安全的” Dictionary(TKey,TValue)


這是一篇翻譯,專門介紹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中到達其​​並發集合 , 並行擴展和並行調試選項有望至少*這個頭痛的事情會自行消失。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM