ConcurrentDictionary.cs 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580
  1. // Licensed to the .NET Foundation under one or more agreements.
  2. // The .NET Foundation licenses this file to you under the Apache 2.0 License.
  3. // See the LICENSE file in the project root for more information.
  4. /*
  5. * WARNING: Auto-generated file (7/18/2012 4:59:53 PM)
  6. *
  7. * Stripped down code based on ndp\clr\src\BCL\System\Collections\Concurrent\ConcurrentDictionary.cs
  8. */
  9. #if NO_CDS_COLLECTIONS
  10. using System;
  11. using System.Collections.Generic;
  12. using System.Collections.ObjectModel;
  13. using System.Diagnostics.CodeAnalysis;
  14. using System.Reflection;
  15. using System.Threading;
  16. namespace System.Collections.Concurrent
  17. {
  18. internal class ConcurrentDictionary<TKey, TValue>
  19. {
  20. /* >>> Code copied from the Array class */
  21. // We impose limits on maximum array lenght in each dimension to allow efficient
  22. // implementation of advanced range check elimination in future.
  23. // Keep in sync with vm\gcscan.cpp and HashHelpers.MaxPrimeArrayLength.
  24. internal const int MaxArrayLength = 0X7FEFFFFF;
  25. /* <<< Code copied from the Array class */
  26. private class Tables
  27. {
  28. internal readonly Node[] m_buckets; // A singly-linked list for each bucket.
  29. internal readonly object[] m_locks; // A set of locks, each guarding a section of the table.
  30. internal volatile int[] m_countPerLock; // The number of elements guarded by each lock.
  31. internal Tables(Node[] buckets, object[] locks, int[] countPerLock)
  32. {
  33. m_buckets = buckets;
  34. m_locks = locks;
  35. m_countPerLock = countPerLock;
  36. }
  37. }
  38. private volatile Tables m_tables; // Internal tables of the dictionary
  39. private readonly IEqualityComparer<TKey> m_comparer; // Key equality comparer
  40. private readonly bool m_growLockArray; // Whether to dynamically increase the size of the striped lock
  41. private int m_budget; // The maximum number of elements per lock before a resize operation is triggered
  42. // The default concurrency level is DEFAULT_CONCURRENCY_MULTIPLIER * #CPUs. The higher the
  43. // DEFAULT_CONCURRENCY_MULTIPLIER, the more concurrent writes can take place without interference
  44. // and blocking, but also the more expensive operations that require all locks become (e.g. table
  45. // resizing, ToArray, Count, etc). According to brief benchmarks that we ran, 4 seems like a good
  46. // compromise.
  47. private const int DEFAULT_CONCURRENCY_MULTIPLIER = 4;
  48. // The default capacity, i.e. the initial # of buckets. When choosing this value, we are making
  49. // a trade-off between the size of a very small dictionary, and the number of resizes when
  50. // constructing a large dictionary. Also, the capacity should not be divisible by a small prime.
  51. private const int DEFAULT_CAPACITY = 31;
  52. // The maximum size of the striped lock that will not be exceeded when locks are automatically
  53. // added as the dictionary grows. However, the user is allowed to exceed this limit by passing
  54. // a concurrency level larger than MAX_LOCK_NUMBER into the constructor.
  55. private const int MAX_LOCK_NUMBER = 1024;
  56. // Whether TValue is a type that can be written atomically (i.e., with no danger of torn reads)
  57. private static readonly bool s_isValueWriteAtomic = IsValueWriteAtomic();
  58. private static bool IsValueWriteAtomic()
  59. {
  60. Type valueType = typeof(TValue);
  61. //
  62. // Section 12.6.6 of ECMA CLI explains which types can be read and written atomically without
  63. // the risk of tearing.
  64. //
  65. // See http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-335.pdf
  66. //
  67. bool isAtomic =
  68. (valueType.GetTypeInfo().IsClass)
  69. || valueType == typeof(Boolean)
  70. || valueType == typeof(Char)
  71. || valueType == typeof(Byte)
  72. || valueType == typeof(SByte)
  73. || valueType == typeof(Int16)
  74. || valueType == typeof(UInt16)
  75. || valueType == typeof(Int32)
  76. || valueType == typeof(UInt32)
  77. || valueType == typeof(Single);
  78. if (!isAtomic && IntPtr.Size == 8)
  79. {
  80. isAtomic |= valueType == typeof(Double) || valueType == typeof(Int64);
  81. }
  82. return isAtomic;
  83. }
  84. public ConcurrentDictionary(IEqualityComparer<TKey> comparer) : this(DefaultConcurrencyLevel, DEFAULT_CAPACITY, true, comparer) { }
  85. public ConcurrentDictionary(int capacity, IEqualityComparer<TKey> comparer) : this(DefaultConcurrencyLevel, capacity, true, comparer) { }
  86. internal ConcurrentDictionary(int concurrencyLevel, int capacity, bool growLockArray, IEqualityComparer<TKey> comparer)
  87. {
  88. if (concurrencyLevel < 1)
  89. {
  90. throw new ArgumentOutOfRangeException("concurrencyLevel");
  91. }
  92. if (capacity < 0)
  93. {
  94. throw new ArgumentOutOfRangeException("capacity");
  95. }
  96. if (comparer == null) throw new ArgumentNullException("comparer");
  97. // The capacity should be at least as large as the concurrency level. Otherwise, we would have locks that don't guard
  98. // any buckets.
  99. if (capacity < concurrencyLevel)
  100. {
  101. capacity = concurrencyLevel;
  102. }
  103. object[] locks = new object[concurrencyLevel];
  104. for (int i = 0; i < locks.Length; i++)
  105. {
  106. locks[i] = new object();
  107. }
  108. int[] countPerLock = new int[locks.Length];
  109. Node[] buckets = new Node[capacity];
  110. m_tables = new Tables(buckets, locks, countPerLock);
  111. m_comparer = comparer;
  112. m_growLockArray = growLockArray;
  113. m_budget = buckets.Length / locks.Length;
  114. }
  115. public bool TryAdd(TKey key, TValue value)
  116. {
  117. if (key == null) throw new ArgumentNullException("key");
  118. TValue dummy;
  119. return TryAddInternal(key, value, false, true, out dummy);
  120. }
  121. public bool TryRemove(TKey key, out TValue value)
  122. {
  123. if (key == null) throw new ArgumentNullException("key");
  124. return TryRemoveInternal(key, out value, false, default(TValue));
  125. }
  126. [SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "Reviewed for thread safety")]
  127. private bool TryRemoveInternal(TKey key, out TValue value, bool matchValue, TValue oldValue)
  128. {
  129. while (true)
  130. {
  131. Tables tables = m_tables;
  132. int bucketNo, lockNo;
  133. GetBucketAndLockNo(m_comparer.GetHashCode(key), out bucketNo, out lockNo, tables.m_buckets.Length, tables.m_locks.Length);
  134. lock (tables.m_locks[lockNo])
  135. {
  136. // If the table just got resized, we may not be holding the right lock, and must retry.
  137. // This should be a rare occurence.
  138. if (tables != m_tables)
  139. {
  140. continue;
  141. }
  142. Node prev = null;
  143. for (Node curr = tables.m_buckets[bucketNo]; curr != null; curr = curr.m_next)
  144. {
  145. if (m_comparer.Equals(curr.m_key, key))
  146. {
  147. if (matchValue)
  148. {
  149. bool valuesMatch = EqualityComparer<TValue>.Default.Equals(oldValue, curr.m_value);
  150. if (!valuesMatch)
  151. {
  152. value = default(TValue);
  153. return false;
  154. }
  155. }
  156. if (prev == null)
  157. {
  158. Volatile.Write<Node>(ref tables.m_buckets[bucketNo], curr.m_next);
  159. }
  160. else
  161. {
  162. prev.m_next = curr.m_next;
  163. }
  164. value = curr.m_value;
  165. tables.m_countPerLock[lockNo]--;
  166. return true;
  167. }
  168. prev = curr;
  169. }
  170. }
  171. value = default(TValue);
  172. return false;
  173. }
  174. }
  175. [SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "Reviewed for thread safety")]
  176. public bool TryGetValue(TKey key, out TValue value)
  177. {
  178. if (key == null) throw new ArgumentNullException("key");
  179. int bucketNo, lockNoUnused;
  180. // We must capture the m_buckets field in a local variable. It is set to a new table on each table resize.
  181. Tables tables = m_tables;
  182. GetBucketAndLockNo(m_comparer.GetHashCode(key), out bucketNo, out lockNoUnused, tables.m_buckets.Length, tables.m_locks.Length);
  183. // We can get away w/out a lock here.
  184. // The Volatile.Read ensures that the load of the fields of 'n' doesn't move before the load from buckets[i].
  185. Node n = Volatile.Read<Node>(ref tables.m_buckets[bucketNo]);
  186. while (n != null)
  187. {
  188. if (m_comparer.Equals(n.m_key, key))
  189. {
  190. value = n.m_value;
  191. return true;
  192. }
  193. n = n.m_next;
  194. }
  195. value = default(TValue);
  196. return false;
  197. }
  198. [SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "Reviewed for thread safety")]
  199. private bool TryAddInternal(TKey key, TValue value, bool updateIfExists, bool acquireLock, out TValue resultingValue)
  200. {
  201. int hashcode = m_comparer.GetHashCode(key);
  202. while (true)
  203. {
  204. int bucketNo, lockNo;
  205. Tables tables = m_tables;
  206. GetBucketAndLockNo(hashcode, out bucketNo, out lockNo, tables.m_buckets.Length, tables.m_locks.Length);
  207. bool resizeDesired = false;
  208. bool lockTaken = false;
  209. try
  210. {
  211. if (acquireLock)
  212. Monitor.Enter(tables.m_locks[lockNo], ref lockTaken);
  213. // If the table just got resized, we may not be holding the right lock, and must retry.
  214. // This should be a rare occurence.
  215. if (tables != m_tables)
  216. {
  217. continue;
  218. }
  219. // Try to find this key in the bucket
  220. Node prev = null;
  221. for (Node node = tables.m_buckets[bucketNo]; node != null; node = node.m_next)
  222. {
  223. if (m_comparer.Equals(node.m_key, key))
  224. {
  225. // The key was found in the dictionary. If updates are allowed, update the value for that key.
  226. // We need to create a new node for the update, in order to support TValue types that cannot
  227. // be written atomically, since lock-free reads may be happening concurrently.
  228. if (updateIfExists)
  229. {
  230. if (s_isValueWriteAtomic)
  231. {
  232. node.m_value = value;
  233. }
  234. else
  235. {
  236. Node newNode = new Node(node.m_key, value, hashcode, node.m_next);
  237. if (prev == null)
  238. {
  239. tables.m_buckets[bucketNo] = newNode;
  240. }
  241. else
  242. {
  243. prev.m_next = newNode;
  244. }
  245. }
  246. resultingValue = value;
  247. }
  248. else
  249. {
  250. resultingValue = node.m_value;
  251. }
  252. return false;
  253. }
  254. prev = node;
  255. }
  256. // The key was not found in the bucket. Insert the key-value pair.
  257. Volatile.Write<Node>(ref tables.m_buckets[bucketNo], new Node(key, value, hashcode, tables.m_buckets[bucketNo]));
  258. checked
  259. {
  260. tables.m_countPerLock[lockNo]++;
  261. }
  262. //
  263. // If the number of elements guarded by this lock has exceeded the budget, resize the bucket table.
  264. // It is also possible that GrowTable will increase the budget but won't resize the bucket table.
  265. // That happens if the bucket table is found to be poorly utilized due to a bad hash function.
  266. //
  267. if (tables.m_countPerLock[lockNo] > m_budget)
  268. {
  269. resizeDesired = true;
  270. }
  271. }
  272. finally
  273. {
  274. if (lockTaken)
  275. Monitor.Exit(tables.m_locks[lockNo]);
  276. }
  277. //
  278. // The fact that we got here means that we just performed an insertion. If necessary, we will grow the table.
  279. //
  280. // Concurrency notes:
  281. // - Notice that we are not holding any locks at when calling GrowTable. This is necessary to prevent deadlocks.
  282. // - As a result, it is possible that GrowTable will be called unnecessarily. But, GrowTable will obtain lock 0
  283. // and then verify that the table we passed to it as the argument is still the current table.
  284. //
  285. if (resizeDesired)
  286. {
  287. GrowTable(tables);
  288. }
  289. resultingValue = value;
  290. return true;
  291. }
  292. }
  293. public ICollection<TValue> Values
  294. {
  295. get { return GetValues(); }
  296. }
  297. private void GrowTable(Tables tables)
  298. {
  299. int locksAcquired = 0;
  300. try
  301. {
  302. // The thread that first obtains m_locks[0] will be the one doing the resize operation
  303. AcquireLocks(0, 1, ref locksAcquired);
  304. // Make sure nobody resized the table while we were waiting for lock 0:
  305. if (tables != m_tables)
  306. {
  307. // We assume that since the table reference is different, it was already resized (or the budget
  308. // was adjusted). If we ever decide to do table shrinking, or replace the table for other reasons,
  309. // we will have to revisit this logic.
  310. return;
  311. }
  312. // Compute the (approx.) total size. Use an Int64 accumulation variable to avoid an overflow.
  313. long approxCount = 0;
  314. for (int i = 0; i < tables.m_countPerLock.Length; i++)
  315. {
  316. approxCount += tables.m_countPerLock[i];
  317. }
  318. //
  319. // If the bucket array is too empty, double the budget instead of resizing the table
  320. //
  321. if (approxCount < tables.m_buckets.Length / 4)
  322. {
  323. m_budget = 2 * m_budget;
  324. if (m_budget < 0)
  325. {
  326. m_budget = int.MaxValue;
  327. }
  328. return;
  329. }
  330. // Compute the new table size. We find the smallest integer larger than twice the previous table size, and not divisible by
  331. // 2,3,5 or 7. We can consider a different table-sizing policy in the future.
  332. int newLength = 0;
  333. bool maximizeTableSize = false;
  334. try
  335. {
  336. checked
  337. {
  338. // Double the size of the buckets table and add one, so that we have an odd integer.
  339. newLength = tables.m_buckets.Length * 2 + 1;
  340. // Now, we only need to check odd integers, and find the first that is not divisible
  341. // by 3, 5 or 7.
  342. while (newLength % 3 == 0 || newLength % 5 == 0 || newLength % 7 == 0)
  343. {
  344. newLength += 2;
  345. }
  346. if (newLength > MaxArrayLength)
  347. {
  348. maximizeTableSize = true;
  349. }
  350. }
  351. }
  352. catch (OverflowException)
  353. {
  354. maximizeTableSize = true;
  355. }
  356. if (maximizeTableSize)
  357. {
  358. newLength = MaxArrayLength;
  359. // We want to make sure that GrowTable will not be called again, since table is at the maximum size.
  360. // To achieve that, we set the budget to int.MaxValue.
  361. //
  362. // (There is one special case that would allow GrowTable() to be called in the future:
  363. // calling Clear() on the ConcurrentDictionary will shrink the table and lower the budget.)
  364. m_budget = int.MaxValue;
  365. }
  366. // Now acquire all other locks for the table
  367. AcquireLocks(1, tables.m_locks.Length, ref locksAcquired);
  368. object[] newLocks = tables.m_locks;
  369. // Add more locks
  370. if (m_growLockArray && tables.m_locks.Length < MAX_LOCK_NUMBER)
  371. {
  372. newLocks = new object[tables.m_locks.Length * 2];
  373. Array.Copy(tables.m_locks, newLocks, tables.m_locks.Length);
  374. for (int i = tables.m_locks.Length; i < newLocks.Length; i++)
  375. {
  376. newLocks[i] = new object();
  377. }
  378. }
  379. Node[] newBuckets = new Node[newLength];
  380. int[] newCountPerLock = new int[newLocks.Length];
  381. // Copy all data into a new table, creating new nodes for all elements
  382. for (int i = 0; i < tables.m_buckets.Length; i++)
  383. {
  384. Node current = tables.m_buckets[i];
  385. while (current != null)
  386. {
  387. Node next = current.m_next;
  388. int newBucketNo, newLockNo;
  389. GetBucketAndLockNo(current.m_hashcode, out newBucketNo, out newLockNo, newBuckets.Length, newLocks.Length);
  390. newBuckets[newBucketNo] = new Node(current.m_key, current.m_value, current.m_hashcode, newBuckets[newBucketNo]);
  391. checked
  392. {
  393. newCountPerLock[newLockNo]++;
  394. }
  395. current = next;
  396. }
  397. }
  398. // Adjust the budget
  399. m_budget = Math.Max(1, newBuckets.Length / newLocks.Length);
  400. // Replace tables with the new versions
  401. m_tables = new Tables(newBuckets, newLocks, newCountPerLock);
  402. }
  403. finally
  404. {
  405. // Release all locks that we took earlier
  406. ReleaseLocks(0, locksAcquired);
  407. }
  408. }
  409. private void GetBucketAndLockNo(
  410. int hashcode, out int bucketNo, out int lockNo, int bucketCount, int lockCount)
  411. {
  412. bucketNo = (hashcode & 0x7fffffff) % bucketCount;
  413. lockNo = bucketNo % lockCount;
  414. }
  415. private static int DefaultConcurrencyLevel
  416. {
  417. get { return DEFAULT_CONCURRENCY_MULTIPLIER * Environment.ProcessorCount; }
  418. }
  419. private void AcquireAllLocks(ref int locksAcquired)
  420. {
  421. // First, acquire lock 0
  422. AcquireLocks(0, 1, ref locksAcquired);
  423. // Now that we have lock 0, the m_locks array will not change (i.e., grow),
  424. // and so we can safely read m_locks.Length.
  425. AcquireLocks(1, m_tables.m_locks.Length, ref locksAcquired);
  426. }
  427. private void AcquireLocks(int fromInclusive, int toExclusive, ref int locksAcquired)
  428. {
  429. object[] locks = m_tables.m_locks;
  430. for (int i = fromInclusive; i < toExclusive; i++)
  431. {
  432. bool lockTaken = false;
  433. try
  434. {
  435. Monitor.Enter(locks[i], ref lockTaken);
  436. }
  437. finally
  438. {
  439. if (lockTaken)
  440. {
  441. locksAcquired++;
  442. }
  443. }
  444. }
  445. }
  446. [SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "Reviewed for thread safety")]
  447. private void ReleaseLocks(int fromInclusive, int toExclusive)
  448. {
  449. for (int i = fromInclusive; i < toExclusive; i++)
  450. {
  451. Monitor.Exit(m_tables.m_locks[i]);
  452. }
  453. }
  454. [SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "ConcurrencyCop just doesn't know about these locks")]
  455. private ReadOnlyCollection<TValue> GetValues()
  456. {
  457. int locksAcquired = 0;
  458. try
  459. {
  460. AcquireAllLocks(ref locksAcquired);
  461. List<TValue> values = new List<TValue>();
  462. for (int i = 0; i < m_tables.m_buckets.Length; i++)
  463. {
  464. Node current = m_tables.m_buckets[i];
  465. while (current != null)
  466. {
  467. values.Add(current.m_value);
  468. current = current.m_next;
  469. }
  470. }
  471. return new ReadOnlyCollection<TValue>(values);
  472. }
  473. finally
  474. {
  475. ReleaseLocks(0, locksAcquired);
  476. }
  477. }
  478. private class Node
  479. {
  480. internal TKey m_key;
  481. internal TValue m_value;
  482. internal volatile Node m_next;
  483. internal int m_hashcode;
  484. internal Node(TKey key, TValue value, int hashcode, Node next)
  485. {
  486. m_key = key;
  487. m_value = value;
  488. m_next = next;
  489. m_hashcode = hashcode;
  490. }
  491. }
  492. }
  493. }
  494. #endif