淺析C#中 ConcurrentDictionary的實現


簡單畫了一張圖 (靈魂畫手 →_→)

如圖 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]));

 

 

為什么這么設計?

對於多線程 這種多個鏈表 多個鎖對象 可以提升 多個線程同時操作的可能性 ,因為很大的程度上寫操作的數據  並不是一個鎖對象負責的。

同時鏈式的存儲 對於添加對象而言 內存的操作更方便

 


免責聲明!

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



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