viksoe.dk

Read Write Lock


This article was submitted .


This class is not my own. It is an extract of a discussion on the Microsoft ATL mailing list on how to implement a read/write lock most efficiently.
class CReadWriteSemaphore
{
private:
   volatile LONG m_nReaders;
   volatile LONG m_nWriters;

   CReadWriteSemaphore(const CReadWriteSemaphore&);

public:
   CReadWriteSemaphore() : m_nReaders(0), m_nWriters(0)
   {
   }

   void LockRead()
   {
      for( ; ; ) {
         ::InterlockedIncrement(&m_nReaders);
         if( m_nWriters == 0 ) break;
         ::InterlockedDecrement(&m_nReaders);
         ::Sleep(0);
      }
   }

   void UnlockRead()
   {
      ::InterlockedDecrement(&m_nReaders);
   }

   void LockWrite()
   {
      for( ; ; ) {
         if( ::InterlockedExchange(&m_nWriters, 1) == 1 ) {
            ::Sleep(0);
         }
         else {
            while( m_nReaders != 0 ) ::Sleep(0);
            break;
         }
      }
   }

   void UnlockWrite()
   {
      ::InterlockedDecrement(&m_nWriters);
   }
};

And here follows a similar implementation using standard Windows semaphore primitives. It might be slightly slower than the one above, since it's using a Windows Semaphore object instead of locked increments. However, the code above requires Windows 2000 to run, and this doesn't.

class CReadWriteSemaphore
{
private:
   CRITICAL_SECTION m_cs;
   HANDLE m_sem;
   LONG m_lMaximumReaders;

   CReadWriteSemaphore(const CReadWriteSemaphore&);

public:
   CReadWriteSemaphore(long lMaxReaders = 10) : 
      m_lMaximumReaders(lMaxReaders)
   {
      ::InitializeCriticalSection(&m_cs);
      m_sem = ::CreateSemaphore(NULL, m_lMaximumReaders, m_lMaximumReaders, NULL);
      _ASSERTE(m_sem!=NULL);
   }

   ~CReadWriteSemaphore()
   {
      ::CloseHandle(m_sem);
      ::DeleteCriticalSection(&m_cs);
   }

   void LockRead()
   {
      ::WaitForSingleObject(m_sem, INFINITE);
   }

   void UnlockRead()
   {
      ::ReleaseSemaphore(m_sem, 1, NULL);
   }

   void LockWrite()
   {
      ::EnterCriticalSection(&m_cs); 
      for( LONG i = 0; i < m_lMaximumReaders; i++ ) {
         ::WaitForSingleObject(m_sem, INFINITE);
      }
      ::LeaveCriticalSection(&m_cs); 
   }

   void UnlockWrite()
   {
      ::ReleaseSemaphore(m_sem, m_lMaximumReaders, NULL);
   }  
};

We can write a more basic class for that matter.

class CReadWriteSemaphore
{
private:
   CRITICAL_SECTION m_cs;
   volatile LONG m_nReaders;

   CReadWriteSemaphore(const CReadWriteSemaphore&);

public:
   CReadWriteSemaphore() : m_nReaders(0)
   {
      ::InitializeCriticalSection(&m_cs);
   }

   ~CReadWriteSemaphore()
   {
      ::DeleteCriticalSection(&m_cs);
   }

   void LockRead()
   {
      ::EnterCriticalSection(&m_cs); 
      ::InterlockedIncrement(&m_nReaders);
      ::LeaveCriticalSection(&m_cs); 
   }

   void UnlockRead()
   {
      ::InterlockedDecrement(&m_nReaders);
   }

   void LockWrite()
   {
      ::EnterCriticalSection(&m_cs); 
      while( m_nReaders > 0 ) ::Sleep(0); 
   }

   void UnlockWrite()
   {
      ::LeaveCriticalSection(&m_cs); 
   }  
};

For completeness, here is a wrapper for the native Windows Vista Slim ReadWrite Lock. Just like the ones above, it's a simple semaphore which doesn't support recursive locks nor upgrading of lock level.

class CReadWriteSemaphore
{
private:
   SRWLOCK m_srw;

   CReadWriteSemaphore(const CReadWriteSemaphore&);

public:
   CReadWriteSemaphore()
   {
      ::InitializeSRWLock(&m_srw);
   }

   void LockRead()
   {
      ::AcquireSRWLockShared(&m_srw);
   }

   void UnlockRead()
   {
      ::ReleaseSRWLockShared(&m_srw);
   }

   void LockWrite()
   {
      ::AcquireSRWLockExclusive(&m_srw);
   }

   void UnlockWrite()
   {
      ::ReleaseSRWLockExclusive(&m_srw);
   }  
};

To the top