Background - (skip this if you want to)
I've written a few multiple reader/single writer (MRSW) implementations before, but they have used either native or lower-level synchronication mechanisms than those provided by C++11 and later. I'm currently updating some older code to be more "modern C++" oriented, as well as more portable, and looked into solutions based on the C++1x standards. One that looked especially interesting due to its simplicity and brevity (code copied below, full thread at How to make a multiple-read/single-write lock from more basic synchronization primitives?) was posted here on SO.
// from link posted above, code originally by qqibrow, edited by sbi at Stack Overflow
class RWLock {
public:
RWLock()
: shared()
, readerQ(), writerQ()
, active_readers(0), waiting_writers(0), active_writers(0)
{}
void ReadLock() {
std::unique_lock<std::mutex> lk(shared);
while( waiting_writers != 0 ) // QUESTION BELOW
readerQ.wait(lk);
++active_readers;
lk.unlock();
}
void ReadUnlock() {
std::unique_lock<std::mutex> lk(shared);
--active_readers;
lk.unlock();
writerQ.notify_one();
}
void WriteLock() {
std::unique_lock<std::mutex> lk(shared);
++waiting_writers;
while( active_readers != 0 || active_writers != 0 ) // QUESTION BELOW
writerQ.wait(lk);
++active_writers;
lk.unlock();
}
void WriteUnlock() {
std::unique_lock<std::mutex> lk(shared);
--waiting_writers;
--active_writers;
if(waiting_writers > 0)
writerQ.notify_one();
else
readerQ.notify_all();
lk.unlock();
}
private:
std::mutex shared;
std::condition_variable readerQ;
std::condition_variable writerQ;
int active_readers;
int waiting_writers;
int active_writers;
};
My Question
My question is about the semantics of mutex locking and access for this MRSW implementation, especially std::unique_lock
(specifically, the lines marked in comments in the above code). Using traditional lower-level synchronization, after ReadLock()
or WriteLock()
acquires exclusive ownwership to the sync object, how can another thread modify waiting_writers
, active_readers
or active_writers
while the lock is acquired? As I mentioned, I've used the two-gate approach before for MRSW, but the simplicity of the above implementation makes me feel like it's either too straightforward to be correct, or I am missing something about the semantics of exclusive locking in C++1x.
I've looked at relevant references, but the semantics for a case like this are not particularly clear (e.g http://en.cppreference.com/w/cpp/thread/unique_lock).
It seems the unlock needs to be handled differently, and I'm probably missing something, but I'd really like some clarity about what it is.
Thanks!