ReaderWriterLockSlim.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474
  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. // Are we on a multiprocessor?
  57. static readonly bool smp;
  58. // Lock specifiation for myLock: This lock protects exactly the local fields associted
  59. // instance of MyReaderWriterLock. It does NOT protect the memory associted with the
  60. // the events that hang off this lock (eg writeEvent, readEvent upgradeEvent).
  61. int myLock;
  62. // Who owns the lock owners > 0 => readers
  63. // owners = -1 means there is one writer, Owners must be >= -1.
  64. int owners;
  65. Thread upgradable_thread;
  66. // These variables allow use to avoid Setting events (which is expensive) if we don't have to.
  67. uint numWriteWaiters; // maximum number of threads that can be doing a WaitOne on the writeEvent
  68. uint numReadWaiters; // maximum number of threads that can be doing a WaitOne on the readEvent
  69. uint numUpgradeWaiters; // maximum number of threads that can be doing a WaitOne on the upgradeEvent (at most 1).
  70. // conditions we wait on.
  71. EventWaitHandle writeEvent; // threads waiting to aquire a write lock go here.
  72. EventWaitHandle readEvent; // threads waiting to aquire a read lock go here (will be released in bulk)
  73. EventWaitHandle upgradeEvent; // thread waiting to upgrade a read lock to a write lock go here (at most one)
  74. //int lock_owner;
  75. // Only set if we are a recursive lock
  76. //Dictionary<int,int> reader_locks;
  77. readonly LockRecursionPolicy recursionPolicy;
  78. bool is_disposed;
  79. static ReaderWriterLockSlim ()
  80. {
  81. smp = Environment.ProcessorCount > 1;
  82. }
  83. public ReaderWriterLockSlim ()
  84. {
  85. // NoRecursion (0) is the default value
  86. }
  87. public ReaderWriterLockSlim (LockRecursionPolicy recursionPolicy)
  88. {
  89. this.recursionPolicy = recursionPolicy;
  90. if (recursionPolicy != LockRecursionPolicy.NoRecursion){
  91. //reader_locks = new Dictionary<int,int> ();
  92. throw new NotImplementedException ("recursionPolicy != NoRecursion not currently implemented");
  93. }
  94. }
  95. public void EnterReadLock ()
  96. {
  97. TryEnterReadLock (-1);
  98. }
  99. public bool TryEnterReadLock (int millisecondsTimeout)
  100. {
  101. if (millisecondsTimeout < Timeout.Infinite)
  102. throw new ArgumentOutOfRangeException ("millisecondsTimeout");
  103. if (is_disposed)
  104. throw new ObjectDisposedException (null);
  105. EnterMyLock ();
  106. while (true){
  107. // Easy case, no contention
  108. // owners >= 0 means there might be readers (but no writer)
  109. if (owners >= 0 && numWriteWaiters == 0){
  110. owners++;
  111. break;
  112. }
  113. // If the request is to probe.
  114. if (millisecondsTimeout == 0){
  115. ExitMyLock ();
  116. return false;
  117. }
  118. // We need to wait. Mark that we have waiters and wait.
  119. if (readEvent == null) {
  120. LazyCreateEvent (ref readEvent, false);
  121. // since we left the lock, start over.
  122. continue;
  123. }
  124. if (!WaitOnEvent (readEvent, ref numReadWaiters, millisecondsTimeout))
  125. return false;
  126. }
  127. ExitMyLock ();
  128. return true;
  129. }
  130. public bool TryEnterReadLock (TimeSpan timeout)
  131. {
  132. int ms = CheckTimeout (timeout);
  133. return TryEnterReadLock (ms);
  134. }
  135. //
  136. // TODO: What to do if we are releasing a ReadLock and we do not own it?
  137. //
  138. public void ExitReadLock ()
  139. {
  140. EnterMyLock ();
  141. Debug.Assert(owners > 0, "ReleasingReaderLock: releasing lock and no read lock taken");
  142. --owners;
  143. ExitAndWakeUpAppropriateWaiters ();
  144. }
  145. public void EnterWriteLock ()
  146. {
  147. TryEnterWriteLock (-1);
  148. }
  149. public bool TryEnterWriteLock (int millisecondsTimeout)
  150. {
  151. EnterMyLock ();
  152. while (true){
  153. // There is no contention, we are done
  154. if (owners == 0){
  155. // Indicate that we have a writer
  156. owners = -1;
  157. break;
  158. }
  159. // If we are the thread that took the Upgradable read lock
  160. if (owners == 1 && upgradable_thread == Thread.CurrentThread){
  161. owners = -1;
  162. break;
  163. }
  164. // If the request is to probe.
  165. if (millisecondsTimeout == 0){
  166. ExitMyLock ();
  167. return false;
  168. }
  169. // We need to wait, figure out what kind of waiting.
  170. if (upgradable_thread == Thread.CurrentThread){
  171. // We are the upgradable thread, register our interest.
  172. if (upgradeEvent == null){
  173. LazyCreateEvent (ref upgradeEvent, false);
  174. // since we left the lock, start over.
  175. continue;
  176. }
  177. if (numUpgradeWaiters > 0){
  178. ExitMyLock ();
  179. throw new ApplicationException ("Upgrading lock to writer lock already in process, deadlock");
  180. }
  181. if (!WaitOnEvent (upgradeEvent, ref numUpgradeWaiters, millisecondsTimeout))
  182. return false;
  183. } else {
  184. if (writeEvent == null){
  185. LazyCreateEvent (ref writeEvent, true);
  186. // since we left the lock, retry
  187. continue;
  188. }
  189. if (!WaitOnEvent (writeEvent, ref numWriteWaiters, millisecondsTimeout))
  190. return false;
  191. }
  192. }
  193. Debug.Assert (owners == -1, "Owners is not -1");
  194. ExitMyLock ();
  195. return true;
  196. }
  197. public bool TryEnterWriteLock (TimeSpan timeout)
  198. {
  199. return TryEnterWriteLock (CheckTimeout (timeout));
  200. }
  201. public void ExitWriteLock ()
  202. {
  203. EnterMyLock ();
  204. Debug.Assert (owners == -1, "Calling ReleaseWriterLock when no write lock is held");
  205. Debug.Assert (numUpgradeWaiters > 0);
  206. upgradable_thread = null;
  207. owners = 0;
  208. ExitAndWakeUpAppropriateWaiters ();
  209. }
  210. public void EnterUpgradeableReadLock ()
  211. {
  212. TryEnterUpgradeableReadLock (-1);
  213. }
  214. //
  215. // Taking the Upgradable read lock is like taking a read lock
  216. // but we limit it to a single upgradable at a time.
  217. //
  218. public bool TryEnterUpgradeableReadLock (int millisecondsTimeout)
  219. {
  220. EnterMyLock ();
  221. while (true){
  222. if (owners == 0 && numWriteWaiters == 0 && upgradable_thread == null){
  223. owners++;
  224. upgradable_thread = Thread.CurrentThread;
  225. break;
  226. }
  227. // If the request is to probe
  228. if (millisecondsTimeout == 0){
  229. ExitMyLock ();
  230. return false;
  231. }
  232. if (readEvent == null){
  233. LazyCreateEvent (ref readEvent, false);
  234. // since we left the lock, start over.
  235. continue;
  236. }
  237. if (!WaitOnEvent (readEvent, ref numReadWaiters, millisecondsTimeout))
  238. return false;
  239. }
  240. ExitMyLock ();
  241. return true;
  242. }
  243. public bool TryEnterUpgradeableReadLock (TimeSpan timeout)
  244. {
  245. return TryEnterUpgradeableReadLock (CheckTimeout (timeout));
  246. }
  247. public void ExitUpgradeableReadLock ()
  248. {
  249. EnterMyLock ();
  250. Debug.Assert (owners > 0, "Releasing an upgradable lock, but there was no reader!");
  251. --owners;
  252. upgradable_thread = null;
  253. ExitAndWakeUpAppropriateWaiters ();
  254. }
  255. public void Dispose ()
  256. {
  257. is_disposed = true;
  258. }
  259. public bool IsReadLockHeld {
  260. get { throw new NotImplementedException (); }
  261. }
  262. public bool IsWriteLockHeld {
  263. get { throw new NotImplementedException (); }
  264. }
  265. public bool IsUpgradeableReadLockHeld {
  266. get { throw new NotImplementedException (); }
  267. }
  268. public int CurrentReadCount {
  269. get { throw new NotImplementedException (); }
  270. }
  271. public int RecursiveReadCount {
  272. get { throw new NotImplementedException (); }
  273. }
  274. public int RecursiveUpgradeCount {
  275. get { throw new NotImplementedException (); }
  276. }
  277. public int RecursiveWriteCount {
  278. get { throw new NotImplementedException (); }
  279. }
  280. public int WaitingReadCount {
  281. get { throw new NotImplementedException (); }
  282. }
  283. public int WaitingUpgradeCount {
  284. get { throw new NotImplementedException (); }
  285. }
  286. public int WaitingWriteCount {
  287. get { throw new NotImplementedException (); }
  288. }
  289. public LockRecursionPolicy RecursionPolicy {
  290. get { return recursionPolicy; }
  291. }
  292. #region Private methods
  293. void EnterMyLock ()
  294. {
  295. if (Interlocked.CompareExchange(ref myLock, 1, 0) != 0)
  296. EnterMyLockSpin ();
  297. }
  298. void EnterMyLockSpin ()
  299. {
  300. for (int i = 0; ;i++) {
  301. if (i < 3 && smp)
  302. Thread.SpinWait (20); // Wait a few dozen instructions to let another processor release lock.
  303. else
  304. Thread.Sleep (0); // Give up my quantum.
  305. if (Interlocked.CompareExchange(ref myLock, 1, 0) == 0)
  306. return;
  307. }
  308. }
  309. void ExitMyLock()
  310. {
  311. Debug.Assert (myLock != 0, "Exiting spin lock that is not held");
  312. myLock = 0;
  313. }
  314. bool MyLockHeld { get { return myLock != 0; } }
  315. /// <summary>
  316. /// Determines the appropriate events to set, leaves the locks, and sets the events.
  317. /// </summary>
  318. private void ExitAndWakeUpAppropriateWaiters()
  319. {
  320. Debug.Assert (MyLockHeld);
  321. // First a writing thread waiting on being upgraded
  322. if (owners == 1 && numUpgradeWaiters != 0){
  323. // Exit before signaling to improve efficiency (wakee will need the lock)
  324. ExitMyLock ();
  325. // release all upgraders (however there can be at most one).
  326. upgradeEvent.Set ();
  327. //
  328. // TODO: What does the following comment mean?
  329. // two threads upgrading is a guarenteed deadlock, so we throw in that case.
  330. } else if (owners == 0 && numWriteWaiters > 0) {
  331. // Exit before signaling to improve efficiency (wakee will need the lock)
  332. ExitMyLock ();
  333. // release one writer.
  334. writeEvent.Set ();
  335. }
  336. else if (owners >= 0 && numReadWaiters != 0) {
  337. // Exit before signaling to improve efficiency (wakee will need the lock)
  338. ExitMyLock ();
  339. // release all readers.
  340. readEvent.Set();
  341. } else
  342. ExitMyLock();
  343. }
  344. /// <summary>
  345. /// A routine for lazily creating a event outside the lock (so if errors
  346. /// happen they are outside the lock and that we don't do much work
  347. /// while holding a spin lock). If all goes well, reenter the lock and
  348. /// set 'waitEvent'
  349. /// </summary>
  350. void LazyCreateEvent(ref EventWaitHandle waitEvent, bool makeAutoResetEvent)
  351. {
  352. Debug.Assert (MyLockHeld);
  353. Debug.Assert (waitEvent == null);
  354. ExitMyLock ();
  355. EventWaitHandle newEvent;
  356. if (makeAutoResetEvent)
  357. newEvent = new AutoResetEvent (false);
  358. else
  359. newEvent = new ManualResetEvent (false);
  360. EnterMyLock ();
  361. // maybe someone snuck in.
  362. if (waitEvent == null)
  363. waitEvent = newEvent;
  364. }
  365. /// <summary>
  366. /// Waits on 'waitEvent' with a timeout of 'millisceondsTimeout.
  367. /// Before the wait 'numWaiters' is incremented and is restored before leaving this routine.
  368. /// </summary>
  369. bool WaitOnEvent (EventWaitHandle waitEvent, ref uint numWaiters, int millisecondsTimeout)
  370. {
  371. Debug.Assert (MyLockHeld);
  372. waitEvent.Reset ();
  373. numWaiters++;
  374. bool waitSuccessful = false;
  375. // Do the wait outside of any lock
  376. ExitMyLock();
  377. try {
  378. waitSuccessful = waitEvent.WaitOne (millisecondsTimeout, false);
  379. } finally {
  380. EnterMyLock ();
  381. --numWaiters;
  382. if (!waitSuccessful)
  383. ExitMyLock ();
  384. }
  385. return waitSuccessful;
  386. }
  387. static int CheckTimeout (TimeSpan timeout)
  388. {
  389. try {
  390. return checked((int) timeout.TotalMilliseconds);
  391. } catch (System.OverflowException) {
  392. throw new ArgumentOutOfRangeException ("timeout");
  393. }
  394. }
  395. #endregion
  396. }
  397. }