SpinLock.cs 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650
  1. // Licensed to the .NET Foundation under one or more agreements.
  2. // The .NET Foundation licenses this file to you under the MIT license.
  3. // See the LICENSE file in the project root for more information.
  4. #pragma warning disable 0420
  5. // =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
  6. //
  7. // A spin lock is a mutual exclusion lock primitive where a thread trying to acquire the lock waits in a loop ("spins")
  8. // repeatedly checking until the lock becomes available. As the thread remains active performing a non-useful task,
  9. // the use of such a lock is a kind of busy waiting and consumes CPU resources without performing real work.
  10. //
  11. // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
  12. using System.Diagnostics;
  13. using System.Runtime.CompilerServices;
  14. namespace System.Threading
  15. {
  16. /// <summary>
  17. /// Provides a mutual exclusion lock primitive where a thread trying to acquire the lock waits in a loop
  18. /// repeatedly checking until the lock becomes available.
  19. /// </summary>
  20. /// <remarks>
  21. /// <para>
  22. /// Spin locks can be used for leaf-level locks where the object allocation implied by using a <see
  23. /// cref="System.Threading.Monitor"/>, in size or due to garbage collection pressure, is overly
  24. /// expensive. Avoiding blocking is another reason that a spin lock can be useful, however if you expect
  25. /// any significant amount of blocking, you are probably best not using spin locks due to excessive
  26. /// spinning. Spinning can be beneficial when locks are fine grained and large in number (for example, a
  27. /// lock per node in a linked list) as well as when lock hold times are always extremely short. In
  28. /// general, while holding a spin lock, one should avoid blocking, calling anything that itself may
  29. /// block, holding more than one spin lock at once, making dynamically dispatched calls (interface and
  30. /// virtuals), making statically dispatched calls into any code one doesn't own, or allocating memory.
  31. /// </para>
  32. /// <para>
  33. /// <see cref="SpinLock"/> should only be used when it's been determined that doing so will improve an
  34. /// application's performance. It's also important to note that <see cref="SpinLock"/> is a value type,
  35. /// for performance reasons. As such, one must be very careful not to accidentally copy a SpinLock
  36. /// instance, as the two instances (the original and the copy) would then be completely independent of
  37. /// one another, which would likely lead to erroneous behavior of the application. If a SpinLock instance
  38. /// must be passed around, it should be passed by reference rather than by value.
  39. /// </para>
  40. /// <para>
  41. /// Do not store <see cref="SpinLock"/> instances in readonly fields.
  42. /// </para>
  43. /// <para>
  44. /// All members of <see cref="SpinLock"/> are thread-safe and may be used from multiple threads
  45. /// concurrently.
  46. /// </para>
  47. /// </remarks>
  48. [DebuggerTypeProxy(typeof(SystemThreading_SpinLockDebugView))]
  49. [DebuggerDisplay("IsHeld = {IsHeld}")]
  50. public struct SpinLock
  51. {
  52. // The current ownership state is a single signed int. There are two modes:
  53. //
  54. // 1) Ownership tracking enabled: the high bit is 0, and the remaining bits
  55. // store the managed thread ID of the current owner. When the 31 low bits
  56. // are 0, the lock is available.
  57. // 2) Performance mode: when the high bit is 1, lock availability is indicated by the low bit.
  58. // When the low bit is 1 -- the lock is held; 0 -- the lock is available.
  59. //
  60. // There are several masks and constants below for convenience.
  61. private volatile int _owner;
  62. // After how many yields, call Sleep(1)
  63. private const int SLEEP_ONE_FREQUENCY = 40;
  64. // After how many yields, check the timeout
  65. private const int TIMEOUT_CHECK_FREQUENCY = 10;
  66. // Thr thread tracking disabled mask
  67. private const int LOCK_ID_DISABLE_MASK = unchecked((int)0x80000000); // 1000 0000 0000 0000 0000 0000 0000 0000
  68. //the lock is held by some thread, but we don't know which
  69. private const int LOCK_ANONYMOUS_OWNED = 0x1; // 0000 0000 0000 0000 0000 0000 0000 0001
  70. // Waiters mask if the thread tracking is disabled
  71. private const int WAITERS_MASK = ~(LOCK_ID_DISABLE_MASK | 1); // 0111 1111 1111 1111 1111 1111 1111 1110
  72. // The Thread tacking is disabled and the lock bit is set, used in Enter fast path to make sure the id is disabled and lock is available
  73. private const int ID_DISABLED_AND_ANONYMOUS_OWNED = unchecked((int)0x80000001); // 1000 0000 0000 0000 0000 0000 0000 0001
  74. // If the thread is unowned if:
  75. // m_owner zero and the thread tracking is enabled
  76. // m_owner & LOCK_ANONYMOUS_OWNED = zero and the thread tracking is disabled
  77. private const int LOCK_UNOWNED = 0;
  78. // The maximum number of waiters (only used if the thread tracking is disabled)
  79. // The actual maximum waiters count is this number divided by two because each waiter increments the waiters count by 2
  80. // The waiters count is calculated by m_owner & WAITERS_MASK 01111....110
  81. private static int MAXIMUM_WAITERS = WAITERS_MASK;
  82. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  83. private static int CompareExchange(ref int location, int value, int comparand, ref bool success)
  84. {
  85. int result = Interlocked.CompareExchange(ref location, value, comparand);
  86. success = (result == comparand);
  87. return result;
  88. }
  89. /// <summary>
  90. /// Initializes a new instance of the <see cref="T:System.Threading.SpinLock"/>
  91. /// structure with the option to track thread IDs to improve debugging.
  92. /// </summary>
  93. /// <remarks>
  94. /// The default constructor for <see cref="SpinLock"/> tracks thread ownership.
  95. /// </remarks>
  96. /// <param name="enableThreadOwnerTracking">Whether to capture and use thread IDs for debugging
  97. /// purposes.</param>
  98. public SpinLock(bool enableThreadOwnerTracking)
  99. {
  100. _owner = LOCK_UNOWNED;
  101. if (!enableThreadOwnerTracking)
  102. {
  103. _owner |= LOCK_ID_DISABLE_MASK;
  104. Debug.Assert(!IsThreadOwnerTrackingEnabled, "property should be false by now");
  105. }
  106. }
  107. /// <summary>
  108. /// Initializes a new instance of the <see cref="T:System.Threading.SpinLock"/>
  109. /// structure with the option to track thread IDs to improve debugging.
  110. /// </summary>
  111. /// <remarks>
  112. /// The default constructor for <see cref="SpinLock"/> tracks thread ownership.
  113. /// </remarks>
  114. /// <summary>
  115. /// Acquires the lock in a reliable manner, such that even if an exception occurs within the method
  116. /// call, <paramref name="lockTaken"/> can be examined reliably to determine whether the lock was
  117. /// acquired.
  118. /// </summary>
  119. /// <remarks>
  120. /// <see cref="SpinLock"/> is a non-reentrant lock, meaning that if a thread holds the lock, it is
  121. /// not allowed to enter the lock again. If thread ownership tracking is enabled (whether it's
  122. /// enabled is available through <see cref="IsThreadOwnerTrackingEnabled"/>), an exception will be
  123. /// thrown when a thread tries to re-enter a lock it already holds. However, if thread ownership
  124. /// tracking is disabled, attempting to enter a lock already held will result in deadlock.
  125. /// </remarks>
  126. /// <param name="lockTaken">True if the lock is acquired; otherwise, false. <paramref
  127. /// name="lockTaken"/> must be initialized to false prior to calling this method.</param>
  128. /// <exception cref="T:System.Threading.LockRecursionException">
  129. /// Thread ownership tracking is enabled, and the current thread has already acquired this lock.
  130. /// </exception>
  131. /// <exception cref="T:System.ArgumentException">
  132. /// The <paramref name="lockTaken"/> argument must be initialized to false prior to calling Enter.
  133. /// </exception>
  134. public void Enter(ref bool lockTaken)
  135. {
  136. // Try to keep the code and branching in this method as small as possible in order to inline the method
  137. int observedOwner = _owner;
  138. if (lockTaken || // invalid parameter
  139. (observedOwner & ID_DISABLED_AND_ANONYMOUS_OWNED) != LOCK_ID_DISABLE_MASK || // thread tracking is enabled or the lock is already acquired
  140. CompareExchange(ref _owner, observedOwner | LOCK_ANONYMOUS_OWNED, observedOwner, ref lockTaken) != observedOwner) //acquiring the lock failed
  141. ContinueTryEnter(Timeout.Infinite, ref lockTaken); // Then try the slow path if any of the above conditions is met
  142. }
  143. /// <summary>
  144. /// Attempts to acquire the lock in a reliable manner, such that even if an exception occurs within
  145. /// the method call, <paramref name="lockTaken"/> can be examined reliably to determine whether the
  146. /// lock was acquired.
  147. /// </summary>
  148. /// <remarks>
  149. /// Unlike <see cref="Enter"/>, TryEnter will not block waiting for the lock to be available. If the
  150. /// lock is not available when TryEnter is called, it will return immediately without any further
  151. /// spinning.
  152. /// </remarks>
  153. /// <param name="lockTaken">True if the lock is acquired; otherwise, false. <paramref
  154. /// name="lockTaken"/> must be initialized to false prior to calling this method.</param>
  155. /// <exception cref="T:System.Threading.LockRecursionException">
  156. /// Thread ownership tracking is enabled, and the current thread has already acquired this lock.
  157. /// </exception>
  158. /// <exception cref="T:System.ArgumentException">
  159. /// The <paramref name="lockTaken"/> argument must be initialized to false prior to calling TryEnter.
  160. /// </exception>
  161. public void TryEnter(ref bool lockTaken)
  162. {
  163. int observedOwner = _owner;
  164. if (((observedOwner & LOCK_ID_DISABLE_MASK) == 0) | lockTaken)
  165. {
  166. // Thread tracking enabled or invalid arg. Take slow path.
  167. ContinueTryEnter(0, ref lockTaken);
  168. }
  169. else if ((observedOwner & LOCK_ANONYMOUS_OWNED) != 0)
  170. {
  171. // Lock already held by someone
  172. lockTaken = false;
  173. }
  174. else
  175. {
  176. // Lock wasn't held; try to acquire it.
  177. CompareExchange(ref _owner, observedOwner | LOCK_ANONYMOUS_OWNED, observedOwner, ref lockTaken);
  178. }
  179. }
  180. /// <summary>
  181. /// Attempts to acquire the lock in a reliable manner, such that even if an exception occurs within
  182. /// the method call, <paramref name="lockTaken"/> can be examined reliably to determine whether the
  183. /// lock was acquired.
  184. /// </summary>
  185. /// <remarks>
  186. /// Unlike <see cref="Enter"/>, TryEnter will not block indefinitely waiting for the lock to be
  187. /// available. It will block until either the lock is available or until the <paramref
  188. /// name="timeout"/>
  189. /// has expired.
  190. /// </remarks>
  191. /// <param name="timeout">A <see cref="System.TimeSpan"/> that represents the number of milliseconds
  192. /// to wait, or a <see cref="System.TimeSpan"/> that represents -1 milliseconds to wait indefinitely.
  193. /// </param>
  194. /// <param name="lockTaken">True if the lock is acquired; otherwise, false. <paramref
  195. /// name="lockTaken"/> must be initialized to false prior to calling this method.</param>
  196. /// <exception cref="T:System.Threading.LockRecursionException">
  197. /// Thread ownership tracking is enabled, and the current thread has already acquired this lock.
  198. /// </exception>
  199. /// <exception cref="T:System.ArgumentException">
  200. /// The <paramref name="lockTaken"/> argument must be initialized to false prior to calling TryEnter.
  201. /// </exception>
  202. /// <exception cref="T:System.ArgumentOutOfRangeException"><paramref name="timeout"/> is a negative
  203. /// number other than -1 milliseconds, which represents an infinite time-out -or- timeout is greater
  204. /// than <see cref="System.Int32.MaxValue"/> milliseconds.
  205. /// </exception>
  206. public void TryEnter(TimeSpan timeout, ref bool lockTaken)
  207. {
  208. // Validate the timeout
  209. long totalMilliseconds = (long)timeout.TotalMilliseconds;
  210. if (totalMilliseconds < -1 || totalMilliseconds > int.MaxValue)
  211. {
  212. throw new System.ArgumentOutOfRangeException(
  213. nameof(timeout), timeout, SR.SpinLock_TryEnter_ArgumentOutOfRange);
  214. }
  215. // Call reliable enter with the int-based timeout milliseconds
  216. TryEnter((int)timeout.TotalMilliseconds, ref lockTaken);
  217. }
  218. /// <summary>
  219. /// Attempts to acquire the lock in a reliable manner, such that even if an exception occurs within
  220. /// the method call, <paramref name="lockTaken"/> can be examined reliably to determine whether the
  221. /// lock was acquired.
  222. /// </summary>
  223. /// <remarks>
  224. /// Unlike <see cref="Enter"/>, TryEnter will not block indefinitely waiting for the lock to be
  225. /// available. It will block until either the lock is available or until the <paramref
  226. /// name="millisecondsTimeout"/> has expired.
  227. /// </remarks>
  228. /// <param name="millisecondsTimeout">The number of milliseconds to wait, or <see
  229. /// cref="System.Threading.Timeout.Infinite"/> (-1) to wait indefinitely.</param>
  230. /// <param name="lockTaken">True if the lock is acquired; otherwise, false. <paramref
  231. /// name="lockTaken"/> must be initialized to false prior to calling this method.</param>
  232. /// <exception cref="T:System.Threading.LockRecursionException">
  233. /// Thread ownership tracking is enabled, and the current thread has already acquired this lock.
  234. /// </exception>
  235. /// <exception cref="T:System.ArgumentException">
  236. /// The <paramref name="lockTaken"/> argument must be initialized to false prior to calling TryEnter.
  237. /// </exception>
  238. /// <exception cref="T:System.ArgumentOutOfRangeException"><paramref name="millisecondsTimeout"/> is
  239. /// a negative number other than -1, which represents an infinite time-out.</exception>
  240. public void TryEnter(int millisecondsTimeout, ref bool lockTaken)
  241. {
  242. int observedOwner = _owner;
  243. if (millisecondsTimeout < -1 || //invalid parameter
  244. lockTaken || //invalid parameter
  245. (observedOwner & ID_DISABLED_AND_ANONYMOUS_OWNED) != LOCK_ID_DISABLE_MASK || //thread tracking is enabled or the lock is already acquired
  246. CompareExchange(ref _owner, observedOwner | LOCK_ANONYMOUS_OWNED, observedOwner, ref lockTaken) != observedOwner) // acquiring the lock failed
  247. ContinueTryEnter(millisecondsTimeout, ref lockTaken); // The call the slow pth
  248. }
  249. /// <summary>
  250. /// Try acquire the lock with long path, this is usually called after the first path in Enter and
  251. /// TryEnter failed The reason for short path is to make it inline in the run time which improves the
  252. /// performance. This method assumed that the parameter are validated in Enter or TryEnter method.
  253. /// </summary>
  254. /// <param name="millisecondsTimeout">The timeout milliseconds</param>
  255. /// <param name="lockTaken">The lockTaken param</param>
  256. private void ContinueTryEnter(int millisecondsTimeout, ref bool lockTaken)
  257. {
  258. // The fast path doesn't throw any exception, so we have to validate the parameters here
  259. if (lockTaken)
  260. {
  261. lockTaken = false;
  262. throw new ArgumentException(SR.SpinLock_TryReliableEnter_ArgumentException);
  263. }
  264. if (millisecondsTimeout < -1)
  265. {
  266. throw new ArgumentOutOfRangeException(
  267. nameof(millisecondsTimeout), millisecondsTimeout, SR.SpinLock_TryEnter_ArgumentOutOfRange);
  268. }
  269. uint startTime = 0;
  270. if (millisecondsTimeout != Timeout.Infinite && millisecondsTimeout != 0)
  271. {
  272. startTime = TimeoutHelper.GetTime();
  273. }
  274. if (IsThreadOwnerTrackingEnabled)
  275. {
  276. // Slow path for enabled thread tracking mode
  277. ContinueTryEnterWithThreadTracking(millisecondsTimeout, startTime, ref lockTaken);
  278. return;
  279. }
  280. // then thread tracking is disabled
  281. // In this case there are three ways to acquire the lock
  282. // 1- the first way the thread either tries to get the lock if it's free or updates the waiters, if the turn >= the processors count then go to 3 else go to 2
  283. // 2- In this step the waiter threads spins and tries to acquire the lock, the number of spin iterations and spin count is dependent on the thread turn
  284. // the late the thread arrives the more it spins and less frequent it check the lock availability
  285. // Also the spins count is increases each iteration
  286. // If the spins iterations finished and failed to acquire the lock, go to step 3
  287. // 3- This is the yielding step, there are two ways of yielding Thread.Yield and Sleep(1)
  288. // If the timeout is expired in after step 1, we need to decrement the waiters count before returning
  289. int observedOwner;
  290. int turn = int.MaxValue;
  291. //***Step 1, take the lock or update the waiters
  292. // try to acquire the lock directly if possible or update the waiters count
  293. observedOwner = _owner;
  294. if ((observedOwner & LOCK_ANONYMOUS_OWNED) == LOCK_UNOWNED)
  295. {
  296. if (CompareExchange(ref _owner, observedOwner | 1, observedOwner, ref lockTaken) == observedOwner)
  297. {
  298. // Acquired lock
  299. return;
  300. }
  301. if (millisecondsTimeout == 0)
  302. {
  303. // Did not acquire lock in CompareExchange and timeout is 0 so fail fast
  304. return;
  305. }
  306. }
  307. else if (millisecondsTimeout == 0)
  308. {
  309. // Did not acquire lock as owned and timeout is 0 so fail fast
  310. return;
  311. }
  312. else //failed to acquire the lock, then try to update the waiters. If the waiters count reached the maximum, just break the loop to avoid overflow
  313. {
  314. if ((observedOwner & WAITERS_MASK) != MAXIMUM_WAITERS)
  315. {
  316. // This can still overflow, but maybe there will never be that many waiters
  317. turn = (Interlocked.Add(ref _owner, 2) & WAITERS_MASK) >> 1;
  318. }
  319. }
  320. // lock acquired failed and waiters updated
  321. //*** Step 2, Spinning and Yielding
  322. var spinner = new SpinWait();
  323. if (turn > PlatformHelper.ProcessorCount)
  324. {
  325. spinner.Count = SpinWait.YieldThreshold;
  326. }
  327. while (true)
  328. {
  329. spinner.SpinOnce(SLEEP_ONE_FREQUENCY);
  330. observedOwner = _owner;
  331. if ((observedOwner & LOCK_ANONYMOUS_OWNED) == LOCK_UNOWNED)
  332. {
  333. int newOwner = (observedOwner & WAITERS_MASK) == 0 ? // Gets the number of waiters, if zero
  334. observedOwner | 1 // don't decrement it. just set the lock bit, it is zero because a previous call of Exit(false) which corrupted the waiters
  335. : (observedOwner - 2) | 1; // otherwise decrement the waiters and set the lock bit
  336. Debug.Assert((newOwner & WAITERS_MASK) >= 0);
  337. if (CompareExchange(ref _owner, newOwner, observedOwner, ref lockTaken) == observedOwner)
  338. {
  339. return;
  340. }
  341. }
  342. if (spinner.Count % TIMEOUT_CHECK_FREQUENCY == 0)
  343. {
  344. // Check the timeout.
  345. if (millisecondsTimeout != Timeout.Infinite && TimeoutHelper.UpdateTimeOut(startTime, millisecondsTimeout) <= 0)
  346. {
  347. DecrementWaiters();
  348. return;
  349. }
  350. }
  351. }
  352. }
  353. /// <summary>
  354. /// decrements the waiters, in case of the timeout is expired
  355. /// </summary>
  356. private void DecrementWaiters()
  357. {
  358. SpinWait spinner = new SpinWait();
  359. while (true)
  360. {
  361. int observedOwner = _owner;
  362. if ((observedOwner & WAITERS_MASK) == 0) return; // don't decrement the waiters if it's corrupted by previous call of Exit(false)
  363. if (Interlocked.CompareExchange(ref _owner, observedOwner - 2, observedOwner) == observedOwner)
  364. {
  365. Debug.Assert(!IsThreadOwnerTrackingEnabled); // Make sure the waiters never be negative which will cause the thread tracking bit to be flipped
  366. break;
  367. }
  368. spinner.SpinOnce();
  369. }
  370. }
  371. /// <summary>
  372. /// ContinueTryEnter for the thread tracking mode enabled
  373. /// </summary>
  374. private void ContinueTryEnterWithThreadTracking(int millisecondsTimeout, uint startTime, ref bool lockTaken)
  375. {
  376. Debug.Assert(IsThreadOwnerTrackingEnabled);
  377. int lockUnowned = 0;
  378. // We are using thread IDs to mark ownership. Snap the thread ID and check for recursion.
  379. // We also must or the ID enablement bit, to ensure we propagate when we CAS it in.
  380. int newOwner = Environment.CurrentManagedThreadId;
  381. if (_owner == newOwner)
  382. {
  383. // We don't allow lock recursion.
  384. throw new LockRecursionException(SR.SpinLock_TryEnter_LockRecursionException);
  385. }
  386. SpinWait spinner = new SpinWait();
  387. // Loop until the lock has been successfully acquired or, if specified, the timeout expires.
  388. do
  389. {
  390. // We failed to get the lock, either from the fast route or the last iteration
  391. // and the timeout hasn't expired; spin once and try again.
  392. spinner.SpinOnce();
  393. // Test before trying to CAS, to avoid acquiring the line exclusively unnecessarily.
  394. if (_owner == lockUnowned)
  395. {
  396. if (CompareExchange(ref _owner, newOwner, lockUnowned, ref lockTaken) == lockUnowned)
  397. {
  398. return;
  399. }
  400. }
  401. // Check the timeout. We only RDTSC if the next spin will yield, to amortize the cost.
  402. if (millisecondsTimeout == 0 ||
  403. (millisecondsTimeout != Timeout.Infinite && spinner.NextSpinWillYield &&
  404. TimeoutHelper.UpdateTimeOut(startTime, millisecondsTimeout) <= 0))
  405. {
  406. return;
  407. }
  408. } while (true);
  409. }
  410. /// <summary>
  411. /// Releases the lock.
  412. /// </summary>
  413. /// <remarks>
  414. /// The default overload of <see cref="Exit()"/> provides the same behavior as if calling <see
  415. /// cref="Exit(bool)"/> using true as the argument, but Exit() could be slightly faster than Exit(true).
  416. /// </remarks>
  417. /// <exception cref="SynchronizationLockException">
  418. /// Thread ownership tracking is enabled, and the current thread is not the owner of this lock.
  419. /// </exception>
  420. public void Exit()
  421. {
  422. //This is the fast path for the thread tracking is disabled, otherwise go to the slow path
  423. if ((_owner & LOCK_ID_DISABLE_MASK) == 0)
  424. ExitSlowPath(true);
  425. else
  426. Interlocked.Decrement(ref _owner);
  427. }
  428. /// <summary>
  429. /// Releases the lock.
  430. /// </summary>
  431. /// <param name="useMemoryBarrier">
  432. /// A Boolean value that indicates whether a memory fence should be issued in order to immediately
  433. /// publish the exit operation to other threads.
  434. /// </param>
  435. /// <remarks>
  436. /// Calling <see cref="Exit(bool)"/> with the <paramref name="useMemoryBarrier"/> argument set to
  437. /// true will improve the fairness of the lock at the expense of some performance. The default <see
  438. /// cref="Enter"/>
  439. /// overload behaves as if specifying true for <paramref name="useMemoryBarrier"/>.
  440. /// </remarks>
  441. /// <exception cref="SynchronizationLockException">
  442. /// Thread ownership tracking is enabled, and the current thread is not the owner of this lock.
  443. /// </exception>
  444. public void Exit(bool useMemoryBarrier)
  445. {
  446. // This is the fast path for the thread tracking is disabled and not to use memory barrier, otherwise go to the slow path
  447. // The reason not to add else statement if the usememorybarrier is that it will add more branching in the code and will prevent
  448. // method inlining, so this is optimized for useMemoryBarrier=false and Exit() overload optimized for useMemoryBarrier=true.
  449. int tmpOwner = _owner;
  450. if ((tmpOwner & LOCK_ID_DISABLE_MASK) != 0 & !useMemoryBarrier)
  451. {
  452. _owner = tmpOwner & (~LOCK_ANONYMOUS_OWNED);
  453. }
  454. else
  455. {
  456. ExitSlowPath(useMemoryBarrier);
  457. }
  458. }
  459. /// <summary>
  460. /// The slow path for exit method if the fast path failed
  461. /// </summary>
  462. /// <param name="useMemoryBarrier">
  463. /// A Boolean value that indicates whether a memory fence should be issued in order to immediately
  464. /// publish the exit operation to other threads
  465. /// </param>
  466. private void ExitSlowPath(bool useMemoryBarrier)
  467. {
  468. bool threadTrackingEnabled = (_owner & LOCK_ID_DISABLE_MASK) == 0;
  469. if (threadTrackingEnabled && !IsHeldByCurrentThread)
  470. {
  471. throw new SynchronizationLockException(SR.SpinLock_Exit_SynchronizationLockException);
  472. }
  473. if (useMemoryBarrier)
  474. {
  475. if (threadTrackingEnabled)
  476. {
  477. Interlocked.Exchange(ref _owner, LOCK_UNOWNED);
  478. }
  479. else
  480. {
  481. Interlocked.Decrement(ref _owner);
  482. }
  483. }
  484. else
  485. {
  486. if (threadTrackingEnabled)
  487. {
  488. _owner = LOCK_UNOWNED;
  489. }
  490. else
  491. {
  492. int tmpOwner = _owner;
  493. _owner = tmpOwner & (~LOCK_ANONYMOUS_OWNED);
  494. }
  495. }
  496. }
  497. /// <summary>
  498. /// Gets whether the lock is currently held by any thread.
  499. /// </summary>
  500. public bool IsHeld
  501. {
  502. get
  503. {
  504. if (IsThreadOwnerTrackingEnabled)
  505. return _owner != LOCK_UNOWNED;
  506. return (_owner & LOCK_ANONYMOUS_OWNED) != LOCK_UNOWNED;
  507. }
  508. }
  509. /// <summary>
  510. /// Gets whether the lock is currently held by any thread.
  511. /// </summary>
  512. /// <summary>
  513. /// Gets whether the lock is held by the current thread.
  514. /// </summary>
  515. /// <remarks>
  516. /// If the lock was initialized to track owner threads, this will return whether the lock is acquired
  517. /// by the current thread. It is invalid to use this property when the lock was initialized to not
  518. /// track thread ownership.
  519. /// </remarks>
  520. /// <exception cref="T:System.InvalidOperationException">
  521. /// Thread ownership tracking is disabled.
  522. /// </exception>
  523. public bool IsHeldByCurrentThread
  524. {
  525. get
  526. {
  527. if (!IsThreadOwnerTrackingEnabled)
  528. {
  529. throw new InvalidOperationException(SR.SpinLock_IsHeldByCurrentThread);
  530. }
  531. return ((_owner & (~LOCK_ID_DISABLE_MASK)) == Environment.CurrentManagedThreadId);
  532. }
  533. }
  534. /// <summary>Gets whether thread ownership tracking is enabled for this instance.</summary>
  535. public bool IsThreadOwnerTrackingEnabled => (_owner & LOCK_ID_DISABLE_MASK) == 0;
  536. #region Debugger proxy class
  537. /// <summary>
  538. /// Internal class used by debug type proxy attribute to display the owner thread ID
  539. /// </summary>
  540. internal class SystemThreading_SpinLockDebugView
  541. {
  542. // SpinLock object
  543. private SpinLock _spinLock;
  544. /// <summary>
  545. /// SystemThreading_SpinLockDebugView constructor
  546. /// </summary>
  547. /// <param name="spinLock">The SpinLock to be proxied.</param>
  548. public SystemThreading_SpinLockDebugView(SpinLock spinLock)
  549. {
  550. // Note that this makes a copy of the SpinLock (struct). It doesn't hold a reference to it.
  551. _spinLock = spinLock;
  552. }
  553. /// <summary>
  554. /// Checks if the lock is held by the current thread or not
  555. /// </summary>
  556. public bool? IsHeldByCurrentThread
  557. {
  558. get
  559. {
  560. try
  561. {
  562. return _spinLock.IsHeldByCurrentThread;
  563. }
  564. catch (InvalidOperationException)
  565. {
  566. return null;
  567. }
  568. }
  569. }
  570. /// <summary>
  571. /// Gets the current owner thread, zero if it is released
  572. /// </summary>
  573. public int? OwnerThreadID
  574. {
  575. get
  576. {
  577. if (_spinLock.IsThreadOwnerTrackingEnabled)
  578. {
  579. return _spinLock._owner;
  580. }
  581. else
  582. {
  583. return null;
  584. }
  585. }
  586. }
  587. /// <summary>
  588. /// Gets whether the lock is currently held by any thread or not.
  589. /// </summary>
  590. public bool IsHeld => _spinLock.IsHeld;
  591. }
  592. #endregion
  593. }
  594. }
  595. #pragma warning restore 0420