A shared recursive mutex in standard C++
If you are on Linux / POSIX platform, you are in luck because C++ mutexes are modelled after POSIX ones. The POSIX ones provide more features, including being recursive, process shared and more. And wrapping POSIX primitives into C++ classes is straight-forward.
Good entry point into POSIX threads documentation.
Recursive property of the mutex operates with term owner, which in case of shared_mutex is not well-defined: several threads may have .lock_shared()
called at the same time.
Assuming owner as a thread which calls .lock()
(not .lock_shared()
!), implementation of recursive shared mutex can be simply derived from shared_mutex
:
class shared_recursive_mutex: public shared_mutex
{
public:
void lock(void) {
std::thread::id this_id = std::this_thread::get_id();
if(owner == this_id) {
// recursive locking
count++;
}
else {
// normal locking
shared_mutex::lock();
owner = this_id;
count = 1;
}
}
void unlock(void) {
if(count > 1) {
// recursive unlocking
count--;
}
else {
// normal unlocking
owner = std::thread::id();
count = 0;
shared_mutex::unlock();
}
}
private:
std::atomic<std::thread::id> owner;
int count;
};
Field .owner
need to be declared as atomic, because in .lock()
method it is checked without protection from concurrent access.
If you want to recursively call .lock_shared()
method, you need to maintain map of owners, and accesses to that map should be protected with some additional mutex.
Allowing thread with active .lock()
to call .lock_shared()
make implementation more complex.
Finally, allowing thread to advance locking from .lock_shared()
to .lock()
is no-no, as it leads to possible deadlock when two threads attempt to perform that advancing.
Again, semantic of recursive shared mutex would be very fragile, so it is better to not use it at all.