Readers/writer mutex pairs

#include <x/rwmutex.H>

x::rwmutex rw;

// ...

std::lock_guard<x::rwmutex::rmutex> lock(rw.r);

// ...

std::lock_guard<x::rwmutex::wmutex> lock(rw.w);

x::rwmutex is a class that instantiates two members, r and w, their types are x::rwmutex::rmutex and x::rwmutex::wmutex.

x::rwmutex::rmutex and x::rwmutex::wmutex implement TimedLockable, and can be used together with the C++ library's locking objects, like std::lock_guard and std::unique_lock, with the following semantics.


An execution thread can own a read mutex or a write mutex, but not both. That's intrinsic to the above definition. What's less obvious is that an execution thread cannot attempt to own a read mutex if it already owns it. Just because a thread owns a read mutex doesn't mean that it can grab another read mutex.

This is because, in order to prevent starvation, an attempt to acquire a write mutex blocks any future read mutex acquisitions, even if there are existing read mutex owners. If a thread owns a read mutex, and there's a waiting write mutex attempt, an attempt to acquire a second read mutex blocks, and because the same thread also has an existing lock on the read mutex, it deadlocks with the thread that's trying to lock the write mutex.

Most of the time, the second read lock will succeed. Until you win the lottery, and get yourself deadlocked.

#include <x/rwmutexdebug.H>

#define rwmutex rwmutexdebug

x::rwmutexdebug has the same API as x::rwmutexdebug, except that it manually tracks all threads and throws an exception if the same thread tries to grab more than one lock.

It should be sufficient to just #define it, on as-needed basis (but only after #include-ing the real class definition, of course). Better yet: use an intermediate typedef that can be switched easily.

The debug class is slow, it should only be used as a temporary debugging measure, to nail down bad locking. Once a bad lock gets detected, a backtrace gets dumped to standard error, and an exception gets thrown, for good measure.