Lock and load

Everyone knows how to do locking in C#, right?

  155 private void Notify(AtomPacket packet)

  156 {

  157   List<CommandListener> listeners; //copy so we don't need the lock long

  158   lock (_commandListeners)

  159     listeners = new List<CommandListener>(_commandListeners.Values);

  160   foreach (var listener in listeners)

  161     listener.Offer(packet);

  162 }

Sample 1 – locking in C# with the lock statement

The point of the argument to the lock statement is to scope the lock to the argument. The lock statement is syntactic sugar for use of Monitor. Without it, the code would look like this:

  155 private void Notify(AtomPacket packet)

  156 {

  157   List<CommandListener> listeners; //copy so we don't need the lock long

  158   Monitor.Enter(_commandListeners);

  159   try

  160   {

  161     listeners = new List<CommandListener>(_commandListeners.Values);

  162   }

  163   finally

  164   {

  165     Monitor.Exit(_commandListeners);

  166   }

  167   foreach (var listener in listeners)

  168     listener.Offer(packet);

  169 }

Sample 2 – locking in C# using Monitor directly

The problem with Monitor is that it makes no distinction between read and write locks. Very often in server scenarios there is frequent reading and rare writing of collections of session state objects. The read operations can and should run concurrently, with threads waiting only for update operations.

To this end, Microsoft provides the ReaderWriterLock class, and its lightweight, high-performance sibling ReaderWriterLockSlim, which is faster and simpler but isn’t intended for recursive lock entry on the same thread.

Here’s a sample from MSDN demonstrating the use of ReaderWriterLockSlim:

    1 using System;

    2 using System.Threading;

    3 using System.Collections.Generic;

    4 

    5 public class SynchronizedCache

    6 {

    7   private ReaderWriterLockSlim cacheLock = new ReaderWriterLockSlim();

    8   private Dictionary<int, string> innerCache = new Dictionary<int, string>();

    9 

   10   public string Read(int key)

   11   {

   12     cacheLock.EnterReadLock();

   13     try

   14     {

   15       return innerCache[key];

   16     }

   17     finally

   18     {

   19       cacheLock.ExitReadLock();

   20     }

   21   }

   22 

   23   public void Add(int key, string value)

   24   {

   25     cacheLock.EnterWriteLock();

   26     try

   27     {

   28       innerCache.Add(key, value);

   29     }

   30     finally

   31     {

   32       cacheLock.ExitWriteLock();

   33     }

   34   }

   35 

   36   public bool AddWithTimeout(int key, string value, int timeout)

   37   {

   38     if (cacheLock.TryEnterWriteLock(timeout))

   39     {

   40       try

   41       {

   42         innerCache.Add(key, value);

   43       }

   44       finally

   45       {

   46         cacheLock.ExitWriteLock();

   47       }

   48       return true;

   49     }

   50     else

   51     {

   52       return false;

   53     }

   54   }

   55 

   56   public AddOrUpdateStatus AddOrUpdate(int key, string value)

   57   {

   58     cacheLock.EnterUpgradeableReadLock();

   59     try

   60     {

   61       string result = null;

   62       if (innerCache.TryGetValue(key, out result))

   63       {

   64         if (result == value)

   65         {

   66           return AddOrUpdateStatus.Unchanged;

   67         }

   68         else

   69         {

   70           cacheLock.EnterWriteLock();

   71           try

   72           {

   73             innerCache[key] = value;

   74           }

   75           finally

   76           {

   77             cacheLock.ExitWriteLock();

   78           }

   79           return AddOrUpdateStatus.Updated;

   80         }

   81       }

   82       else

   83       {

   84         cacheLock.EnterWriteLock();

   85         try

   86         {

   87           innerCache.Add(key, value);

   88         }

   89         finally

   90         {

   91           cacheLock.ExitWriteLock();

   92         }

   93         return AddOrUpdateStatus.Added;

   94       }

   95     }

   96     finally

   97     {

   98       cacheLock.ExitUpgradeableReadLock();

   99     }

  100   }

  101 

  102   public void Delete(int key)

  103   {

  104     cacheLock.EnterWriteLock();

  105     try

  106     {

  107       innerCache.Remove(key);

  108     }

  109     finally

  110     {

  111       cacheLock.ExitWriteLock();

  112     }

  113   }

  114 

  115   public enum AddOrUpdateStatus

  116   {

  117     Added,

  118     Updated,

  119     Unchanged

  120   };

  121 }

Sample 3 – use of ReaderWriterLockSlim (courtesy MSDN)

Notice that the scope of the lock is the instance of ReaderWriterLockSlim, which is much less useful, especially in server code which needs scoping. You could keep a dictionary of lock objects, but then you’d need to lock that. So I wrote ReadWriteMonitor, which is a static class that emulates Monitor but using ReaderWriterLockSlim.

    1 using System;

    2 using System.Collections.Generic;

    3 using System.Threading;

    4 

    5 namespace Locksmith

    6 {

    7   public static class ReadWriteMonitor

    8   {

    9     static Dictionary<object, ReaderWriterLockSlim> _locks = new Dictionary<object, ReaderWriterLockSlim>();

   10     static ReaderWriterLockSlim _locksLock = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion);

   11 

   12     private static ReaderWriterLockSlim GetReaderWriterLock(object obj)

   13     {

   14       try

   15       {

   16         _locksLock.EnterUpgradeableReadLock();

   17         if (!_locks.ContainsKey(obj))

   18         {

   19           _locksLock.EnterWriteLock();

   20           _locks.Add(obj, new ReaderWriterLockSlim());

   21         }

   22         return _locks[obj];

   23       }

   24       finally

   25       {

   26         _locksLock.ExitUpgradeableReadLock();

   27         if (_locksLock.IsWriteLockHeld) _locksLock.ExitWriteLock();

   28       }

   29     }

   30 

   31     public static void EnterReadLock(object obj)

   32     {

   33       ReaderWriterLockSlim rwl = GetReaderWriterLock(obj);

   34       rwl.EnterReadLock();

   35     }

   36 

   37     public static void ExitReadLock(object obj)

   38     {

   39       ReaderWriterLockSlim rwl = GetReaderWriterLock(obj);

   40       rwl.ExitReadLock();

   41     }

   42 

   43     public static void EnterUpgradeableReadLock(object obj)

   44     {

   45       ReaderWriterLockSlim rwl = GetReaderWriterLock(obj);

   46       rwl.EnterUpgradeableReadLock();

   47     }

   48 

   49     public static void ExitUpgradeableReadLock(object obj)

   50     {

   51       ReaderWriterLockSlim rwl = GetReaderWriterLock(obj);

   52       rwl.ExitUpgradeableReadLock();

   53     }

   54 

   55     public static void EnterWriteLock(object obj)

   56     {

   57       ReaderWriterLockSlim rwl = GetReaderWriterLock(obj);

   58       rwl.EnterWriteLock();

   59     }

   60 

   61     public static void ExitWriteLock(object obj)

   62     {

   63       ReaderWriterLockSlim rwl = GetReaderWriterLock(obj);

   64       rwl.ExitWriteLock();

   65     }

   66 

   67     public static void FreeLock(object obj)

   68     {

   69       try

   70       {

   71         _locksLock.EnterUpgradeableReadLock();

   72         if (_locks.ContainsKey(obj))

   73         {

   74           _locksLock.EnterWriteLock();

   75           ReaderWriterLockSlim rwl = _locks[obj];

   76           if ((rwl.WaitingReadCount == 0) && (rwl.WaitingUpgradeCount == 0) && (rwl.WaitingWriteCount == 0))

   77           {

   78             _locks.Remove(obj);

   79           }

   80           else

   81           {

   82             throw new InvalidOperationException("ReaderWriterLockSlim object cannot be freed because locks are still held");

   83           }

   84         }

   85       }

   86       finally

   87       {

   88         _locksLock.ExitUpgradeableReadLock();

   89         if (_locksLock.IsWriteLockHeld)

   90           _locksLock.ExitWriteLock();

   91       }

   92 

   93     }

   94 

   95     public static bool IsUpgradeableLockHeld(object obj)

   96     {

   97       ReaderWriterLockSlim rwl = GetReaderWriterLock(obj);

   98       return rwl.IsUpgradeableReadLockHeld;

   99     }

  100 

  101     public static bool IsWriteLockHeld(object obj)

  102     {

  103       ReaderWriterLockSlim rwl = GetReaderWriterLock(obj);

  104       return rwl.IsWriteLockHeld;

  105     }

  106   }

  107 }

Sample 4 - ReadWriteMonitor

You use it very much as in the sample for direct use of Monitor, excepting of course that you have a choice of three types of lock: read, upgradeable and write. 

  155 private void Notify(AtomPacket packet)

  156 {

  157   List<CommandListener> listeners; //copy so we don't need the lock long

  158   ReadWriteMonitor.EnterReadLock(_commandListeners);

  159   try

  160   {

  161     listeners = new List<CommandListener>(_commandListeners.Values);

  162   }

  163   finally

  164   {

  165     ReadWriteMonitor.ExitReadLock(_commandListeners);

  166   }

  167   foreach (var listener in listeners)

  168     listener.Offer(packet);

  169 }

Sample 5 – sample 2 rewritten to use ReadWriteMonitor read locks

Broadly speaking, an upgradeable read lock imposes higher overheads for the sake of slightly different semantics that are better if you do upgrade (it affects the sequence in which write lock requests are queued). If you know you won’t upgrade, as in the above sample, then upgradeability is a waste of time in every sense. For more information regarding these modes and their use see ReaderWriterLockSlim on MSDN or in Visual Studio Help.

ReadWriteMonitor itself provides a good example of conditions under which you would use an upgradeable read lock, even if it (obviously) uses ReaderWriterLockSlim directly. _locks is a hash table of ReaderWriterLockSlim instances keyed on obj. _locksLock is the locking object for _locks. We use an upgradeable read lock to determine whether there is a locking object for obj. If there is, we return it and release the upgradeable read lock. If there isn’t, we upgrade to a write lock, create a new instance of ReaderWriterLockSlim and add it to the collection, then return it and release the write lock.

   12 private static ReaderWriterLockSlim GetReaderWriterLock(object obj)

   13 {

   14   try

   15   {

   16     _locksLock.EnterUpgradeableReadLock();

   17     if (!_locks.ContainsKey(obj))

   18     {

   19       _locksLock.EnterWriteLock();

   20       _locks.Add(obj, new ReaderWriterLockSlim());

   21     }

   22     return _locks[obj];

   23   }

   24   finally

   25   {

   26     _locksLock.ExitUpgradeableReadLock();

   27     if (_locksLock.IsWriteLockHeld) _locksLock.ExitWriteLock();

   28   }

   29 }

Sample 6 – appropriate use of upgradeable locks
Published 04-08-2009 14:28 by peterw