.NET的ConcurrentDictionary,執行緒安全集合類
阿新 • • 發佈:2019-01-04
ConcurrentDictionary 是.NET 4.0裡面新增的號稱執行緒安全的集合類。
那麼自然,我們會預期ConcurrentDictionary 中的程式碼是執行緒安全的(至少幾個關鍵方法是執行緒安全的).
舉個例子,使用者可能會預期GetOrAdd中的方法當Key不存在的時候只執行一次Add的委託,第二次呼叫GetOrAdd就應該直接取回剛才生成的值了.
參考一下以下程式碼:
public static void Test() { var concurentDictionary = new ConcurrentDictionary<int, int>(); var w = new ManualResetEvent(false); int timedCalled = 0; var threads = new List<Thread>(); for (int i = 0; i < Environment.ProcessorCount; i++) { threads.Add(new Thread(() => { w.WaitOne(); concurentDictionary.GetOrAdd(1, i1 => { Interlocked.Increment(ref timedCalled); return 1; }); })); threads.Last().Start(); } w.Set();//release all threads to start at the same timeThread.Sleep(100); Console.WriteLine(timedCalled);// output is 4, means call initial 4 times //Console.WriteLine(concurentDictionary.Keys.Count); }
GetOrAdd方法的定義就是按照Key獲取一個Value,如果Key不存在,那麼呼叫Func<T> 新增一個鍵值對.
按照ConcurrentDictionary的定義, 我預期這個Add應該只被呼叫一次
可是上面那段程式碼的執行結果表明, Interlocked.Increment(ref timedCalled); 被呼叫了4次,真是尷尬啊
用於初始化值的委託還真的是可以多次執行的,所以
- 要麼保證委託中的程式碼重複執行不會有問題
- 要麼使用執行緒安全的初始化方法,例如Lazy<T>
public static void Test() { var concurentDictionary = new ConcurrentDictionary<int, int>(); var w = new ManualResetEvent(false); int timedCalled = 0; var threads = new List<Thread>(); Lazy<int> lazy = new Lazy<int>(() => { Interlocked.Increment(ref timedCalled); return 1; }); for (int i = 0; i < Environment.ProcessorCount; i++) { threads.Add(new Thread(() => { w.WaitOne(); concurentDictionary.GetOrAdd(1, i1 => { return lazy.Value; }); })); threads.Last().Start(); } w.Set();//release all threads to start at the same time Thread.Sleep(100); Console.WriteLine(timedCalled);// output is 1 }
附: 註釋中也不說一下這個初始化方法會被多次呼叫,如果不是偶然遇到這個問題,估計永遠都不知道
//// Summary: // Adds a key/value pair to the System.Collections.Concurrent.ConcurrentDictionary<TKey,TValue> // if the key does not already exist. //// Parameters: // key: // The key of the element to add. //// valueFactory: // The function used to generate a value for the key //// Returns: // The value for the key. This will be either the existing value for the key // if the key is already in the dictionary, or the new value for the key as // returned by valueFactory if the key was not in the dictionary. //// Exceptions: // System.ArgumentNullException: // key is a null reference (Nothing in Visual Basic).-or-valueFactory is a null // reference (Nothing in Visual Basic). //// System.OverflowException: // The dictionary contains too many elements.
該集合類中所有使用Func<T>的方法也存在類似的問題
希望能給還不知道該問題的朋友提個醒,避免不必要的BUG