Recursive, Upgradable Read/Write Lock for Windows

Environment: VC6, Windows NT/2000; should work on 98/Me, but not tested there

Read/write lock is a synchronization mechanism that protects a shared resource in a multithreaded environment. The standard mutex (and critical section) object provided by Windows allows only one thread a time to access the resource. In many cases, this is overkill. Let's assume we have two or more reader threads that read the contents of the resource without changing it, and one or more writer threads that can both read and write to the resource. In this case, we can relax the rules of protection a little bit:

  • A reader can access the resource when no writers are active.
  • A writer can access the resource when no other reader or writer is active.
  • Several readers can access the resource concurrently.

Many operating systems implement this kind of protection natively. Unfortunately, no native synchronization object provided by Windows offers such a functionality. Nevertheless, read/write locks can be built in Windows, based on available standard synchronization objects.

This article offers two lock objects:

  • KReadWriteLock—simple read/write lock, and
  • KReadWriteLockEx—extended read/write lock.

They have almost identical interfaces, but slightly different implementation.

KReadWriteLock Class

class KReadWriteLock
{
  KReadWriteLock( KReadWriteLock const& ); // not implemented
  void operator=( KReadWriteLock const& ); // not implemented

public:
  KReadWriteLock();

  bool LockRead(    DWORD Timeout = INFINITE );
  bool LockWrite(   DWORD Timeout = INFINITE );
  bool UnlockRead(  DWORD Timeout = INFINITE );
  bool UnlockWrite( DWORD Timeout = INFINITE );
};

Locking rules for KReadWriteLock are as follows:

  • Any number of readers can acquire the mutex for read (LockRead()).
  • Only a single writer can acquire the mutex for write (LockWrite()).
  • No other writers or readers can hold the mutex while the writer uses it.

KReadWriteLock prefers neither writers nor readers. In other words, if both writer(s) and reader(s) are waiting for the lock, any of them can grab the lock when it is released by the previous owner.

KReadWriteLock is recursive, but not upgradable. This means that:

  • If you have a read lock, you can safely acquire another read lock.
  • If you have a read lock, you CANNOT acquire a write lock (deadlock!).
  • If you have a write lock, you can safely acquire another write lock.
  • If you have a write lock, you CANNOT acquire a read lock (deadlock!).

KReadWriteLockEx Class

class KReadWriteLockEx
{
  ReadWriteLockEx( KReadWriteLockEx const& ); // not implemented
  void operator=( KReadWriteLockEx const& );  // not implemented

public:
  KReadWriteLockEx();

  bool LockRead(    DWORD Timeout = INFINITE );
  bool LockWrite(   DWORD Timeout = INFINITE );
  bool UnlockRead(  DWORD Timeout = INFINITE );
  bool UnlockWrite( DWORD Timeout = INFINITE );
};

KReadWriteLockEx offers more flexibility: it is both recursive and upgradable. That is, if you have a read lock, you can safely acquire a write lock and vice versa. Furthermore, you can even overlap locks:

KReadWriteLockEx Lock;
Lock.LockRead();
Lock.LockWrite();
Lock.UnlockRead();
Lock.UnlockWrite();

KReadWriteLockEx prefers writers over readers. That is, if a writer is waiting on the lock, no new readers are allowed to access the resource. Existing readers can continue to use the resource until they release the lock. This prevents so-called "writer starvation".

For all this flexibility, KReadWriteLockEx is slower than KreadWriteLock, takes more memory, and is more complex in its implementation. This is because KReadWriteLockEx must manually keep track of what threads currently own the lock. In the case of KReadWriteLock, this job is done by the operating system.

Note: neither KReadWriteLock nor KReadWriteLockEx will work between processes. However, they can be extended to do so.

Helper Classes

Helper classes KReadLock, KWriteLock, KReadLockEx, and KWriteLockEx are used for automatic acquisition and release of locks. They are similar in nature to the CSingleLock class from MFC. Their interfaces are almost identical and look like this:

class KReadLock
{
    KReadLock(KReadWriteLock& Lock, bool bInitialLock = true);
    ~KReadLockTempl();

    bool Lock(  DWORD Timeout = INFINITE);
    bool Unlock(DWORD Timeout = INFINITE);
};

Use of methods is pretty straightforward and similar to CSingleLock. Note, that unlike CSingleLock, helper classes will acquire the lock in constructor by default.

Downloads

Download demo project - 26 Kb
Another demo - using helper classes - 23 Kb
Download source - 4 Kb