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