簡單畫了一張圖 (靈魂畫手 →_→)
如圖 ConcurrentDictionary 其中有個tables 對象主要存儲,而這個 tables 是一個 很多區塊的 數組 ,每個區塊 又是一個node的鏈表 (ps: 一個node 就是一個key value 對)
具體實現如下(ps 代碼摘自 net4.5):
private volatile ConcurrentDictionary<TKey, TValue>.Tables m_tables; private class Tables { internal readonly ConcurrentDictionary<TKey, TValue>.Node[] m_buckets; internal readonly object[] m_locks; internal volatile int[] m_countPerLock; internal readonly IEqualityComparer<TKey> m_comparer; internal Tables(ConcurrentDictionary<TKey, TValue>.Node[] buckets, object[] locks, int[] countPerLock, IEqualityComparer<TKey> comparer) { this.m_buckets = buckets; this.m_locks = locks; this.m_countPerLock = countPerLock; this.m_comparer = comparer; } } private class Node { internal TKey m_key; internal TValue m_value; internal volatile ConcurrentDictionary<TKey, TValue>.Node m_next; internal int m_hashcode; internal Node(TKey key, TValue value, int hashcode, ConcurrentDictionary<TKey, TValue>.Node next) { this.m_key = key; this.m_value = value; this.m_next = next; this.m_hashcode = hashcode; } }
看 這個Node類是一個帶next 指針的結構 ,一個node就是鏈表 ,而Tables類中 m_buckets 變量便是一個 存儲了n個鏈表的列表結構
其中Tables類中有個變量名為 countPerLock 類型為 int[] 便是圖中最下面那個框 這個,這個變量 主要是用來 統計字典中數據的個數 ,這個 countPerLock 與m_buckets 數量 一一對應,一個node對應countPerLock 中的一個元素。Count()這個方法便是主要使用這個元素經行統計。這樣的好處是不用遍歷node鏈表。
最重要的是Tables 中m_locks的實現。 這是一個鎖的列表 其中用來控制 多線程讀取修改時 控制的區塊 。
當字典初始化的時候 m_locks 的數量為 cpu內核數*4(ps:例如i7 就是8*4=32) 而 m_buckets 數量初始化是31(有個小條件是m_locks>=m_buckets 時 m_locks=m_buckets) 所以i7 下 m_buckets 和 m_locks 都是32個 ,理論上再,不添加新節點的時候 一個區塊對應 一個鎖。
m_locks 初始化時是默認值是 cpu內核數*4 最大值是 1024個
m_buckets初始化默認值 31 最大值是2146435071
也就是說 如果字典中數據量大的時候 是一個鎖對象 對應n個Node鏈表。
關於ConcurrentDictionary中所有讀取操作
例如Keys Values Count 這類的屬性 會對鎖定 m_locks 中所有的鎖對象 所以需要謹慎使用。
而常用的索引器[]和 TryGetValue 等方法 未鎖定任何鎖對象,並通過 Volatile.Read 原子性讀取 對應的Node鏈表 遍歷中所有元素 直到找到 對應的key 為止。
關於ConcurrentDictionary中所有寫操作
當添加一個新數據時 方法會計算key的hashcode 是放到哪個node里表中 然后鎖定對應的鎖對象,自后 通過 Volatile.Write 方法 替換新的Node的指向 , 這時node為新的值 它的next 指向原先的Node值。
private void GetBucketAndLockNo(int hashcode, out int bucketNo, out int lockNo, int bucketCount, int lockCount) { bucketNo = (hashcode & int.MaxValue) % bucketCount; //Node的位置 lockNo = bucketNo % lockCount; // 鎖位置 } Volatile.Write<ConcurrentDictionary<TKey, TValue>.Node>(ref tables.m_buckets[bucketNo], new ConcurrentDictionary<TKey, TValue>.Node(key, value, hashCode, tables.m_buckets[bucketNo]));
為什么這么設計?
對於多線程 這種多個鏈表 多個鎖對象 可以提升 多個線程同時操作的可能性 ,因為很大的程度上寫操作的數據 並不是一個鎖對象負責的。
同時鏈式的存儲 對於添加對象而言 內存的操作更方便