Read/Write Lock AKA Reader/Writer Lock
A Read/Write Lock is a performance improvement over a standard mutex for cases where reads outnumber writes. The idea is to divide locks up into two classes: reading and writing. Multiple simultaneous read locks may be held, but write locks are exclusively held.
The exclusive writing lock ensures that race conditions do not occur, since if one client is writing to the data no other client may read or write. Also, the allowance for multiple simultaneous read locks decreases resource contention since multiple readers can safely use the shared data. This increases performance over a standard mutex for the assumed usage pattern of frequent simultaneous reads and infrequent writes.
However, a simple Read/Write Lock implementation that avoids starvation in all cases is not easy to create. The following implementation prioritises write locks, and so does not allow the assumed greater number of readers to starve writers. This is accomplished by making new read locks wait if there is a writer waiting for a lock.
The disadvantage of this implementation is that if usage is not as expected writers may overwhelm the mutex and starve readers indefinitely.
Listing 1. Read/Write Lock favouring writers
// multiple clients may read simultaneously // but write access is exclusive // writers are favoured over readers class ReadWriteMutex : boost::noncopyable { public: ReadWriteMutex() : m_readers(0), m_pendingWriters(0), m_currentWriter(false) {} // local class has access to ReadWriteMutex private members, as required class ScopedReadLock : boost::noncopyable { public: ScopedReadLock(ReadWriteMutex& rwLock) : m_rwLock(rwLock) { m_rwLock.acquireReadLock(); } ~ScopedReadLock() { m_rwLock.releaseReadLock(); } private: ReadWriteMutex& m_rwLock; }; class ScopedWriteLock : boost::noncopyable { public: ScopedWriteLock(ReadWriteMutex& rwLock) : m_rwLock(rwLock) { m_rwLock.acquireWriteLock(); } ~ScopedWriteLock() { m_rwLock.releaseWriteLock(); } private: ReadWriteMutex& m_rwLock; }; private: // data boost::mutex m_mutex; unsigned int m_readers; boost::condition m_noReaders; unsigned int m_pendingWriters; bool m_currentWriter; boost::condition m_writerFinished; private: // internal locking functions void acquireReadLock() { boost::mutex::scoped_lock lock(m_mutex); // require a while loop here, since when the writerFinished condition is notified // we should not allow readers to lock if there is a writer waiting // if there is a writer waiting, we continue waiting while(m_pendingWriters != 0 || m_currentWriter) { m_writerFinished.wait(lock); } ++m_readers; } void releaseReadLock() { boost::mutex::scoped_lock lock(m_mutex); --m_readers; if(m_readers == 0) { // must notify_all here, since if there are multiple waiting writers // they should all be woken (they continue to acquire the lock exclusively though) m_noReaders.notify_all(); } } // this function is currently not exception-safe: // if the wait calls throw, m_pendingWriter can be left in an inconsistent state void acquireWriteLock() { boost::mutex::scoped_lock lock(m_mutex); // ensure subsequent readers block ++m_pendingWriters; // ensure all reader locks are released while(m_readers > 0) { m_noReaders.wait(lock); } // only continue when the current writer has finished // and another writer has not been woken first while(m_currentWriter) { m_writerFinished.wait(lock); } --m_pendingWriters; m_currentWriter = true; } void releaseWriteLock() { boost::mutex::scoped_lock lock(m_mutex); m_currentWriter = false; m_writerFinished.notify_all(); } };
