.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: checked5: {
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: try17: {
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: else39: {
40: node.m_next = node3;
41: }
42: resultingValue = value;43: }
44: else45: {
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: finally60: {
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: }
我们看着这个实现完全是无锁的,但也是不准确的,因为如果正好碰上扩展就不对了,不过对于并发字典这没什么问题。