Monitor For Thread Locking Using ReaderWriterLocking

This article explains use of a Monitor for thread locking using ReaderWriterLocking.

If you are looking for a multi-threaded application then you must have already tried our very old lock statement (synclock in VB.NET). The lock statement is the most common practice to make certain blocks of code thread safe, or in other words, it blocks all other threads from entering the block that one thread is actually executing. The lock statement actually produces a Monitor. Enter in a try and Monitor. Exit in the finally. Hence it exclusively locks the block for you.

Limitation of Monitor

Now what is the limitation of the lock statement? The first is that it locks the entire object and does not provide you a chance to provide a Timeout seed. Hence it might produce a deadlock situation in certain scenarios, but you can easily handle this using "Monitor.TryEnter" instead of Enter that lets you specify a timeout.

But the second and most important limitation of Monitor is that it always provides exclusive locking. Thus if a certain resource is read and written by multiple threads at a time then exclusive locking might produce inefficiencies. It is similar to what we see in databases. Reads generally cannot be inconsistent when multiple threads are reading it simultaneously, only if some thread updates it. Hence exclusive locking is inefficient in the case of reading a resource.

ReaderWriterLock allows shared locks together with exclusive locks. Hence it is possible to read the same resource using sharedlocking. Each time the Readerlock is invoked, the Lock count is increased but allows you to read the block of code inside it. ReaderWriterLock also provides an option for exclusive locking using the WriteLock function to eliminate inconsistent reads. Let's see how to use this with code.

  1. static ReaderWriterLock locker = new ReaderWriterLock();  
  2. public static List Repository = new List();  
  3.   
  4. public static long Sum()  
  5. {  
  6.   
  7.     locker.AcquireReaderLock(1000);  
  8.   
  9.     long counter = 0;  
  10.     try  
  11.     {  
  12.         for (int i = 0; i < Repository.Count; i++)  
  13.             Interlocked.Add(ref counter, Repository[i]);  
  14.     }  
  15.     finally  
  16.     {  
  17.         locker.ReleaseReaderLock();  
  18.     }  
  19.     return counter;  
  20. }  
  21.   
  22. public static void AddToRepository(int nos)  
  23. {  
  24.     if (locker.IsWriterLockHeld)  
  25.         return;  
  26.   
  27.     locker.AcquireWriterLock(1000);  
  28.   
  29.     try  
  30.     {  
  31.         for (int i = 0; i < nos; i++)  
  32.             Repository.Add(i);  
  33.     }  
  34.     catch { }  
  35.   
  36.     locker.ReleaseWriterLock();  
  37. }  

AddToRepository is only used to add some integer values to a List. You can easily lock the Repository using a lock(Repository) statement, and that will mean an exclusive locking will be established. On the other hand, the code written inside AddToRepository also produces exclusive locking using an AquireWriterLock function. You can optionally specify the timeout value for the WriterLock. (specify TimeSman.Infinite when you don’t need timeout).

But on the method Sum that actually reads from the list an exclusive locking with create inefficiencies. We have used AquireReaderLock in this case to create Shared locking for the block. Hence concurrent reads can take place only when no WriterLock is established, but concurrent writer locks are not possible.

The ReaderWriterLock also has a method to escalate from a Reader to a Writer and vice versa using UpgradeToWriterLock or DowngradeFromWriterLock.

Every lock should always specify its Release function in a finally.

I hope this would be useful.

Thanks for reading.