ConcurrentDictionary實現


.Net4 增加的System.Collection.Concurrent線程安全的集合實現,這兒有MS的性能測試報告:Thread-safe Collections in .NET Framework 4 and Their Performance Characteristics。總的來說效率還是很不錯的,為了提高效率用了一些技巧,接口上也多是TryXXX。

         ConcurrentDictionary采用Level-Lock方式,和Dictionary一樣還是只有一個buckets,只是內部lock時,采用locknum=key.GetHashcode%lockCount, lock(objects[num]),方式,默認情況下DefaultConcurrencyLevel=4 * Environment.ProcessorCount;  也就是4*CPU核心數。相比於一個lock object,在高並發使用多個lock object可以很大減少lock等待的可能;但在整個集合操作(如:Count,Clear,Expend..)還是需要全部加鎖后操作。

         為啥接口都變成TryXXX呢:因為如Add 和Remove之間存在競爭,不能再像單線程集合那樣簡單的拋出異常,不能拋出異常那就得要一個是否操作成功的返回碼~

 

下面是節點數據結構,算法也有很大的區別,這里使用類,hashtable ,Dictionary都用的是struct。問題在於如果使用HashTable雙hash算法,對同一個數組就沒法做Level啦。Dictionary分別兩個數組Bluckets和Entitys,同樣無法區分level。至於hash算法比起前兩者都要簡單不少呢。所以采用了class 可以使用m_next連接方式解決沖突,m_ext 使用volatile可解決多線程緩存同步問題。

來咱來計算一下內存占用問題吧使用class方式無疑對於簡單字段要占用多不少內存,就拿int-int來算:

  Dictionary 4*5=20字節/node

ConcurrentDictionary 在x64下:8+4+4+4+8+8+8=42字節/node 要多占不少內存那。不過對於Node=null時,空位多的時候也能節省部分內存,不過一般空位多了不應該~

------  blucket(8)+Node(key(4)+value(4)+hash(4)+next(8)+methodRef(8))+syncblk(8))

*有個想法咱吧Dictionary改造一下位Level Lock,用多個Dict,前面文章有測試效果,還不錯。

   1:  private class Node //不再是struck 了呢
   2:  {
   3:      internal TKey m_key;
   4:      internal TValue m_value;
   5:      internal volatile Node m_next;  //在這兒volatile很重要
   6:      internal int m_hashcode;
   7:      internal node()
   8:      {
   9:          this.m_key = key;
  10:          this.m_value = value;
  11:          this.m_next = next;
  12:          this.m_hashcode = hashcode;
  13:      }
  14:  }
 
 
下面是Add的實現:
   1:  private bool TryAddInternal(TKey key, TValue value, bool updateIfExists, bool acquireLock, out TValue resultingValue)
   2:  {
   3:      int hashCode = this.m_comparer.GetHashCode(key);
   4:      checked
   5:      {
   6:          ConcurrentDictionary<TKey, TValue>.Node[] buckets;
   7:          bool flag;
   8:          while (true)
   9:          {
  10:              buckets = this.m_buckets;
  11:              int num;
  12:              int num2;
  13:              this.GetBucketAndLockNo(hashCode, out num, out num2, buckets.Length);//計算Hash地址和新的節點落在那個Lock上面
  14:              flag = false;
  15:              bool flag2 = false;
  16:              try
  17:              {
  18:                  if (acquireLock)
  19:                  {
  20:                      Monitor.Enter(this.m_locks[num2], ref flag2);
  21:                  }
  22:                  if (buckets != this.m_buckets)
  23:                  {
  24:                      continue; //這兒很重要Enter之前如果不做這個判斷,可能因為字段擴容而丟失值
  25:                  }
  26:                  ConcurrentDictionary<TKey, TValue>.Node node = null;
  27:                  for (ConcurrentDictionary<TKey, TValue>.Node node2 = buckets[num]; node2 != null; node2 = node2.m_next)
  28:                  {
  29:                      if (this.m_comparer.Equals(node2.m_key, key))
  30:                      {
  31:                          if (updateIfExists)
  32:                          {
  33:                              ConcurrentDictionary<TKey, TValue>.Node node3 = new ConcurrentDictionary<TKey, TValue>.Node(node2.m_key, value, hashCode, node2.m_next);
  34:                              if (node == null)
  35:                              {
  36:                                  buckets[num] = node3;
  37:                              }
  38:                              else
  39:                              {
  40:                                  node.m_next = node3;
  41:                              }
  42:                              resultingValue = value;
  43:                          }
  44:                          else
  45:                          {
  46:                              resultingValue = node2.m_value;
  47:                          }
  48:                          return false;
  49:                      }
  50:                      node = node2;
  51:                  }
  52:                  buckets[num] = new ConcurrentDictionary<TKey, TValue>.Node(key, value, hashCode, buckets[num]);
  53:                  this.m_countPerLock[num2]++;
  54:                  if (this.m_countPerLock[num2] > buckets.Length / this.m_locks.Length)//這個擴容標准比Dictionary要嚴一點哈~
  55:                  {
  56:                      flag = true;
  57:                  }
  58:              }
  59:              finally
  60:              {
  61:                  if (flag2)
  62:                  {
  63:                      Monitor.Exit(this.m_locks[num2]);
  64:                  }
  65:              }
  66:              break;
  67:          }
  68:          if (flag)
  69:          {
  70:              this.GrowTable(buckets);
  71:          }
  72:          resultingValue = value;
  73:          return true;
  74:      }
  75:  }

 

比較有意思的是枚舉器的實現,因為我們這個字段並沒有維護version版本號哦~~~,看看他是怎么解決並發問題的:

   1:  public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
   2:  {
   3:      ConcurrentDictionary<TKey, TValue>.Node[] buckets = this.m_buckets;
   4:      for (int i = 0; i < buckets.Length; i++)
   5:      {
   6:          ConcurrentDictionary<TKey, TValue>.Node node = buckets[i];
   7:          Thread.MemoryBarrier();
   8:          while (node != null)
   9:          {
  10:              yield return new KeyValuePair<TKey, TValue>(node.m_key, node.m_value);
  11:              node = node.m_next;
  12:          }
  13:      }
  14:      yield break;
  15:  }

 

我們看着這個實現完全是無鎖的,但也是不准確的,因為如果正好碰上擴展就不對了,不過對於並發字典這沒什么問題。


免責聲明!

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



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