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:  }

我们看着这个实现完全是无锁的,但也是不准确的,因为如果正好碰上扩展就不对了,不过对于并发字典这没什么问题。

原文地址:https://www.cnblogs.com/lulu/p/2519675.html