Nonexclusive locking lets you limit concurrency. The nonexclusive locking constructs are Semaphore(Slim) and ReaderWriterLock(Slim).

SemaphoreSlim

The SemaphoreSlim works like a night club with a bouncer. It has a certain capacity and will let only so much people (threads) in as specified by the capacity.

public class TheNightClub
{
     // Capacity of 3
     static SemaphoreSlim _semaphoreSlim = new SemaphoreSlim (3); 
     static void Main()
     {
         for (int i = 1; i <= 5; i++)
            new Thread (Enter).Start (i);
     }

    static void Enter (object id)
    {
        Console.WriteLine (id + " wants to enter"); 
        _semaphoreSlim.Wait();
        Console.WriteLine (id + " is in!");
        Thread.Sleep (1000 * (int) id); 
        Console.WriteLine (id + " is leaving"); 
       _semaphoreSlim.Release();
    } 
}

This will let the first 3 Threads to the second WriteLine statement, where as the fourth only reaches the Wait statement (the fifth probably too). When then after a second the first threads Thread.Sleep expired, it will leave and Release the lock. Then the 4th Thread is allowed to enter. The fifth thread then is only allowed when the second thread also releases the lock.

ReaderWriterLockSlim

The Monitor or lock restrict for read and write similiar. If a thread wants to write, none other can write or read and vice versa. Yet there are in some scenarios where you have lots of reads, and seldom update operations.

This is what the ReaderWriterLockSlim was built for.

It has two kinds of locks:

  1. write lock is universally exclusive (like standard lock)
  2. reader lock is compatible with other read locks.

The write lock, blocks all other threads that want to read or write, where when there is no write lock, any number of threads can read the state.

It is applied like this:

public class ReaderWriterLockkDemo
{
     private readonly _rwLock = new ReaderWriterLockSlim();

     public void Read()
     {
         _rwLock.EnterReadLock();
         try
         {
            DoRead();
         }
         finally
         { 
            _rwLock.ExitReadLock();
         }
     }

     public void Write(object writer)
     {
          _rwLock.EnterWriteLock();
          try
          {
             SomeWriteOperation(writer);
          }
          finally
          {
             _rwLock.ExitWriteLock();
          }
      }
}

The ReadWriterLockSlim also supports a TryEnter methods like above with the Monitor.TryEnter that accepts a timeout.

Upgradeable locks

You can upgrade a readlock to a writelock in an atomic operation. This helps to avoid locking on read to check the state, releasing the read lock, acquiring a write lock and writing the state.  In this chain of events, another thread could potentially write the state between releasing the read lock and acquiring the write lock.

For this you use the method EnterUpgradeableReadLock, perform read based activities, call to EnterWriteLock (converts the upgradeable lock), perform write based activity, ExitWriteLock and Release the lock with ExitUpgradeableLock.

Recursion/Reentrancy is not allowed with ReaderWriterLockSlim. So the following throws an exception:

var rwLock = new ReaderWriterLockSlim();

rwLock.EnterReadLock();
rwLock.EnterReadLock();
rwLock.ExitReadLock();
rwLock.ExitReadLock();

Summary

We looked at why and how mutable shared state can lead to issues with asynchronous programming. We then proceeded to look at what can be done and looked in more detail at the locking mechanisms to synchronize access for multiple threads in concurrent environments.

For this we specifically explored the Monitor and lock statements, that work as an exclusive lock. That is they restrict the access to exactly one Thread at a time.

We also looked at two non exclusive locking mechanisms, the semaphore slim and the ReaderWriterLockSlim classes.

For further read look at the following posts:

Categories: TPL

0 Comments

Leave a Reply