ReaderWriterLockSlim.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564
  1. //
  2. // System.Threading.ReaderWriterLockSlim.cs
  3. //
  4. // Authors:
  5. // Miguel de Icaza ([email protected])
  6. // Dick Porter ([email protected])
  7. // Jackson Harper ([email protected])
  8. // Lluis Sanchez Gual ([email protected])
  9. // Marek Safar ([email protected])
  10. //
  11. // Copyright 2004-2008 Novell, Inc (http://www.novell.com)
  12. // Copyright 2003, Ximian, Inc.
  13. //
  14. // NoRecursion code based on the blog post from Vance Morrison:
  15. // http://blogs.msdn.com/vancem/archive/2006/03/28/563180.aspx
  16. //
  17. // Recursion code based on Mono's implementation of ReaderWriterLock.
  18. //
  19. // Permission is hereby granted, free of charge, to any person obtaining
  20. // a copy of this software and associated documentation files (the
  21. // "Software"), to deal in the Software without restriction, including
  22. // without limitation the rights to use, copy, modify, merge, publish,
  23. // distribute, sublicense, and/or sell copies of the Software, and to
  24. // permit persons to whom the Software is furnished to do so, subject to
  25. // the following conditions:
  26. //
  27. // The above copyright notice and this permission notice shall be
  28. // included in all copies or substantial portions of the Software.
  29. //
  30. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  31. // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  32. // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  33. // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  34. // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  35. // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  36. // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  37. //
  38. using System;
  39. using System.Collections;
  40. using System.Collections.Generic;
  41. using System.Security.Permissions;
  42. using System.Diagnostics;
  43. using System.Threading;
  44. namespace System.Threading {
  45. //
  46. // This implementation is based on the light-weight
  47. // Reader/Writer lock sample from Vance Morrison's blog:
  48. //
  49. // http://blogs.msdn.com/vancem/archive/2006/03/28/563180.aspx
  50. //
  51. // And in Mono's ReaderWriterLock
  52. //
  53. [HostProtectionAttribute(SecurityAction.LinkDemand, MayLeakOnAbort = true)]
  54. [HostProtectionAttribute(SecurityAction.LinkDemand, Synchronization = true, ExternalThreading = true)]
  55. public class ReaderWriterLockSlim : IDisposable {
  56. sealed class LockDetails
  57. {
  58. public int ThreadId;
  59. public int ReadLocks;
  60. }
  61. // Are we on a multiprocessor?
  62. static readonly bool smp;
  63. // Lock specifiation for myLock: This lock protects exactly the local fields associted
  64. // instance of MyReaderWriterLock. It does NOT protect the memory associted with the
  65. // the events that hang off this lock (eg writeEvent, readEvent upgradeEvent).
  66. int myLock;
  67. // Who owns the lock owners > 0 => readers
  68. // owners = -1 means there is one writer, Owners must be >= -1.
  69. int owners;
  70. Thread upgradable_thread;
  71. Thread write_thread;
  72. // These variables allow use to avoid Setting events (which is expensive) if we don't have to.
  73. uint numWriteWaiters; // maximum number of threads that can be doing a WaitOne on the writeEvent
  74. uint numReadWaiters; // maximum number of threads that can be doing a WaitOne on the readEvent
  75. uint numUpgradeWaiters; // maximum number of threads that can be doing a WaitOne on the upgradeEvent (at most 1).
  76. // conditions we wait on.
  77. EventWaitHandle writeEvent; // threads waiting to aquire a write lock go here.
  78. EventWaitHandle readEvent; // threads waiting to aquire a read lock go here (will be released in bulk)
  79. EventWaitHandle upgradeEvent; // thread waiting to upgrade a read lock to a write lock go here (at most one)
  80. //int lock_owner;
  81. // Only set if we are a recursive lock
  82. //Dictionary<int,int> reader_locks;
  83. readonly LockRecursionPolicy recursionPolicy;
  84. LockDetails[] read_locks = new LockDetails [8];
  85. static ReaderWriterLockSlim ()
  86. {
  87. smp = Environment.ProcessorCount > 1;
  88. }
  89. public ReaderWriterLockSlim ()
  90. {
  91. // NoRecursion (0) is the default value
  92. }
  93. public ReaderWriterLockSlim (LockRecursionPolicy recursionPolicy)
  94. {
  95. this.recursionPolicy = recursionPolicy;
  96. if (recursionPolicy != LockRecursionPolicy.NoRecursion){
  97. //reader_locks = new Dictionary<int,int> ();
  98. throw new NotImplementedException ("recursionPolicy != NoRecursion not currently implemented");
  99. }
  100. }
  101. public void EnterReadLock ()
  102. {
  103. TryEnterReadLock (-1);
  104. }
  105. public bool TryEnterReadLock (int millisecondsTimeout)
  106. {
  107. if (millisecondsTimeout < Timeout.Infinite)
  108. throw new ArgumentOutOfRangeException ("millisecondsTimeout");
  109. if (read_locks == null)
  110. throw new ObjectDisposedException (null);
  111. if (Thread.CurrentThread == write_thread)
  112. throw new LockRecursionException ("Read lock cannot be acquired while write lock is held");
  113. EnterMyLock ();
  114. LockDetails ld = GetReadLockDetails (Thread.CurrentThread.ManagedThreadId, true);
  115. if (ld.ReadLocks != 0) {
  116. ExitMyLock ();
  117. throw new LockRecursionException ("Recursive read lock can only be aquired in SupportsRecursion mode");
  118. }
  119. ++ld.ReadLocks;
  120. while (true){
  121. // Easy case, no contention
  122. // owners >= 0 means there might be readers (but no writer)
  123. if (owners >= 0 && numWriteWaiters == 0){
  124. owners++;
  125. break;
  126. }
  127. // If the request is to probe.
  128. if (millisecondsTimeout == 0){
  129. ExitMyLock ();
  130. return false;
  131. }
  132. // We need to wait. Mark that we have waiters and wait.
  133. if (readEvent == null) {
  134. LazyCreateEvent (ref readEvent, false);
  135. // since we left the lock, start over.
  136. continue;
  137. }
  138. if (!WaitOnEvent (readEvent, ref numReadWaiters, millisecondsTimeout))
  139. return false;
  140. }
  141. ExitMyLock ();
  142. return true;
  143. }
  144. public bool TryEnterReadLock (TimeSpan timeout)
  145. {
  146. return TryEnterReadLock (CheckTimeout (timeout));
  147. }
  148. //
  149. // TODO: What to do if we are releasing a ReadLock and we do not own it?
  150. //
  151. public void ExitReadLock ()
  152. {
  153. EnterMyLock ();
  154. if (owners < 1) {
  155. ExitMyLock ();
  156. throw new SynchronizationLockException ("Releasing lock and no read lock taken");
  157. }
  158. --owners;
  159. --GetReadLockDetails (Thread.CurrentThread.ManagedThreadId, false).ReadLocks;
  160. ExitAndWakeUpAppropriateWaiters ();
  161. }
  162. public void EnterWriteLock ()
  163. {
  164. TryEnterWriteLock (-1);
  165. }
  166. public bool TryEnterWriteLock (int millisecondsTimeout)
  167. {
  168. if (millisecondsTimeout < Timeout.Infinite)
  169. throw new ArgumentOutOfRangeException ("millisecondsTimeout");
  170. if (read_locks == null)
  171. throw new ObjectDisposedException (null);
  172. if (IsWriteLockHeld)
  173. throw new LockRecursionException ();
  174. EnterMyLock ();
  175. LockDetails ld = GetReadLockDetails (Thread.CurrentThread.ManagedThreadId, false);
  176. if (ld != null && ld.ReadLocks > 0) {
  177. ExitMyLock ();
  178. throw new LockRecursionException ("Write lock cannot be acquired while read lock is held");
  179. }
  180. while (true){
  181. // There is no contention, we are done
  182. if (owners == 0){
  183. // Indicate that we have a writer
  184. owners = -1;
  185. write_thread = Thread.CurrentThread;
  186. break;
  187. }
  188. // If we are the thread that took the Upgradable read lock
  189. if (owners == 1 && upgradable_thread == Thread.CurrentThread){
  190. owners = -1;
  191. write_thread = Thread.CurrentThread;
  192. break;
  193. }
  194. // If the request is to probe.
  195. if (millisecondsTimeout == 0){
  196. ExitMyLock ();
  197. return false;
  198. }
  199. // We need to wait, figure out what kind of waiting.
  200. if (upgradable_thread == Thread.CurrentThread){
  201. // We are the upgradable thread, register our interest.
  202. if (upgradeEvent == null){
  203. LazyCreateEvent (ref upgradeEvent, false);
  204. // since we left the lock, start over.
  205. continue;
  206. }
  207. if (numUpgradeWaiters > 0){
  208. ExitMyLock ();
  209. throw new ApplicationException ("Upgrading lock to writer lock already in process, deadlock");
  210. }
  211. if (!WaitOnEvent (upgradeEvent, ref numUpgradeWaiters, millisecondsTimeout))
  212. return false;
  213. } else {
  214. if (writeEvent == null){
  215. LazyCreateEvent (ref writeEvent, true);
  216. // since we left the lock, retry
  217. continue;
  218. }
  219. if (!WaitOnEvent (writeEvent, ref numWriteWaiters, millisecondsTimeout))
  220. return false;
  221. }
  222. }
  223. Debug.Assert (owners == -1, "Owners is not -1");
  224. ExitMyLock ();
  225. return true;
  226. }
  227. public bool TryEnterWriteLock (TimeSpan timeout)
  228. {
  229. return TryEnterWriteLock (CheckTimeout (timeout));
  230. }
  231. public void ExitWriteLock ()
  232. {
  233. EnterMyLock ();
  234. if (owners != -1) {
  235. ExitMyLock ();
  236. throw new SynchronizationLockException ("Calling ExitWriterLock when no write lock is held");
  237. }
  238. //Debug.Assert (numUpgradeWaiters > 0);
  239. if (upgradable_thread == Thread.CurrentThread)
  240. owners = 1;
  241. else
  242. owners = 0;
  243. write_thread = null;
  244. ExitAndWakeUpAppropriateWaiters ();
  245. }
  246. public void EnterUpgradeableReadLock ()
  247. {
  248. TryEnterUpgradeableReadLock (-1);
  249. }
  250. //
  251. // Taking the Upgradable read lock is like taking a read lock
  252. // but we limit it to a single upgradable at a time.
  253. //
  254. public bool TryEnterUpgradeableReadLock (int millisecondsTimeout)
  255. {
  256. if (millisecondsTimeout < Timeout.Infinite)
  257. throw new ArgumentOutOfRangeException ("millisecondsTimeout");
  258. if (read_locks == null)
  259. throw new ObjectDisposedException (null);
  260. if (IsUpgradeableReadLockHeld)
  261. throw new LockRecursionException ();
  262. if (IsWriteLockHeld)
  263. throw new LockRecursionException ();
  264. EnterMyLock ();
  265. while (true){
  266. if (owners == 0 && numWriteWaiters == 0 && upgradable_thread == null){
  267. owners++;
  268. upgradable_thread = Thread.CurrentThread;
  269. break;
  270. }
  271. // If the request is to probe
  272. if (millisecondsTimeout == 0){
  273. ExitMyLock ();
  274. return false;
  275. }
  276. if (readEvent == null){
  277. LazyCreateEvent (ref readEvent, false);
  278. // since we left the lock, start over.
  279. continue;
  280. }
  281. if (!WaitOnEvent (readEvent, ref numReadWaiters, millisecondsTimeout))
  282. return false;
  283. }
  284. ExitMyLock ();
  285. return true;
  286. }
  287. public bool TryEnterUpgradeableReadLock (TimeSpan timeout)
  288. {
  289. return TryEnterUpgradeableReadLock (CheckTimeout (timeout));
  290. }
  291. public void ExitUpgradeableReadLock ()
  292. {
  293. EnterMyLock ();
  294. Debug.Assert (owners > 0, "Releasing an upgradable lock, but there was no reader!");
  295. --owners;
  296. upgradable_thread = null;
  297. ExitAndWakeUpAppropriateWaiters ();
  298. }
  299. public void Dispose ()
  300. {
  301. read_locks = null;
  302. }
  303. public bool IsReadLockHeld {
  304. get { return RecursiveReadCount != 0; }
  305. }
  306. public bool IsWriteLockHeld {
  307. get { return RecursiveWriteCount != 0; }
  308. }
  309. public bool IsUpgradeableReadLockHeld {
  310. get { return RecursiveUpgradeCount != 0; }
  311. }
  312. public int CurrentReadCount {
  313. get { return owners & 0xFFFFFFF; }
  314. }
  315. public int RecursiveReadCount {
  316. get {
  317. EnterMyLock ();
  318. LockDetails ld = GetReadLockDetails (Thread.CurrentThread.ManagedThreadId, false);
  319. int count = ld == null ? 0 : ld.ReadLocks;
  320. ExitMyLock ();
  321. return count;
  322. }
  323. }
  324. public int RecursiveUpgradeCount {
  325. get { return upgradable_thread == Thread.CurrentThread ? 1 : 0; }
  326. }
  327. public int RecursiveWriteCount {
  328. get { return write_thread == Thread.CurrentThread ? 1 : 0; }
  329. }
  330. public int WaitingReadCount {
  331. get { return (int) numReadWaiters; }
  332. }
  333. public int WaitingUpgradeCount {
  334. get { return (int) numUpgradeWaiters; }
  335. }
  336. public int WaitingWriteCount {
  337. get { return (int) numWriteWaiters; }
  338. }
  339. public LockRecursionPolicy RecursionPolicy {
  340. get { return recursionPolicy; }
  341. }
  342. #region Private methods
  343. void EnterMyLock ()
  344. {
  345. if (Interlocked.CompareExchange(ref myLock, 1, 0) != 0)
  346. EnterMyLockSpin ();
  347. }
  348. void EnterMyLockSpin ()
  349. {
  350. for (int i = 0; ;i++) {
  351. if (i < 3 && smp)
  352. Thread.SpinWait (20); // Wait a few dozen instructions to let another processor release lock.
  353. else
  354. Thread.Sleep (0); // Give up my quantum.
  355. if (Interlocked.CompareExchange(ref myLock, 1, 0) == 0)
  356. return;
  357. }
  358. }
  359. void ExitMyLock()
  360. {
  361. Debug.Assert (myLock != 0, "Exiting spin lock that is not held");
  362. myLock = 0;
  363. }
  364. bool MyLockHeld { get { return myLock != 0; } }
  365. /// <summary>
  366. /// Determines the appropriate events to set, leaves the locks, and sets the events.
  367. /// </summary>
  368. private void ExitAndWakeUpAppropriateWaiters()
  369. {
  370. Debug.Assert (MyLockHeld);
  371. // First a writing thread waiting on being upgraded
  372. if (owners == 1 && numUpgradeWaiters != 0){
  373. // Exit before signaling to improve efficiency (wakee will need the lock)
  374. ExitMyLock ();
  375. // release all upgraders (however there can be at most one).
  376. upgradeEvent.Set ();
  377. //
  378. // TODO: What does the following comment mean?
  379. // two threads upgrading is a guarenteed deadlock, so we throw in that case.
  380. } else if (owners == 0 && numWriteWaiters > 0) {
  381. // Exit before signaling to improve efficiency (wakee will need the lock)
  382. ExitMyLock ();
  383. // release one writer.
  384. writeEvent.Set ();
  385. }
  386. else if (owners >= 0 && numReadWaiters != 0) {
  387. // Exit before signaling to improve efficiency (wakee will need the lock)
  388. ExitMyLock ();
  389. // release all readers.
  390. readEvent.Set();
  391. } else
  392. ExitMyLock();
  393. }
  394. /// <summary>
  395. /// A routine for lazily creating a event outside the lock (so if errors
  396. /// happen they are outside the lock and that we don't do much work
  397. /// while holding a spin lock). If all goes well, reenter the lock and
  398. /// set 'waitEvent'
  399. /// </summary>
  400. void LazyCreateEvent(ref EventWaitHandle waitEvent, bool makeAutoResetEvent)
  401. {
  402. Debug.Assert (MyLockHeld);
  403. Debug.Assert (waitEvent == null);
  404. ExitMyLock ();
  405. EventWaitHandle newEvent;
  406. if (makeAutoResetEvent)
  407. newEvent = new AutoResetEvent (false);
  408. else
  409. newEvent = new ManualResetEvent (false);
  410. EnterMyLock ();
  411. // maybe someone snuck in.
  412. if (waitEvent == null)
  413. waitEvent = newEvent;
  414. }
  415. /// <summary>
  416. /// Waits on 'waitEvent' with a timeout of 'millisceondsTimeout.
  417. /// Before the wait 'numWaiters' is incremented and is restored before leaving this routine.
  418. /// </summary>
  419. bool WaitOnEvent (EventWaitHandle waitEvent, ref uint numWaiters, int millisecondsTimeout)
  420. {
  421. Debug.Assert (MyLockHeld);
  422. waitEvent.Reset ();
  423. numWaiters++;
  424. bool waitSuccessful = false;
  425. // Do the wait outside of any lock
  426. ExitMyLock();
  427. try {
  428. waitSuccessful = waitEvent.WaitOne (millisecondsTimeout, false);
  429. } finally {
  430. EnterMyLock ();
  431. --numWaiters;
  432. if (!waitSuccessful)
  433. ExitMyLock ();
  434. }
  435. return waitSuccessful;
  436. }
  437. static int CheckTimeout (TimeSpan timeout)
  438. {
  439. try {
  440. return checked((int) timeout.TotalMilliseconds);
  441. } catch (System.OverflowException) {
  442. throw new ArgumentOutOfRangeException ("timeout");
  443. }
  444. }
  445. LockDetails GetReadLockDetails (int threadId, bool create)
  446. {
  447. int i;
  448. LockDetails ld;
  449. for (i = 0; i < read_locks.Length; ++i) {
  450. ld = read_locks [i];
  451. if (ld == null)
  452. break;
  453. if (ld.ThreadId == threadId)
  454. return ld;
  455. }
  456. if (!create)
  457. return null;
  458. if (i == read_locks.Length)
  459. Array.Resize (ref read_locks, read_locks.Length * 2);
  460. ld = read_locks [i] = new LockDetails ();
  461. ld.ThreadId = threadId;
  462. return ld;
  463. }
  464. #endregion
  465. }
  466. }