C++11 Multithreading - The Readers-Writers Problem
The reader and writer processes share the following data structures:
semaphore rw_mutex = 1; semaphore mutex = 1; int read_count = 0;
The semaphores mutex and rw_mutex are initialized to 1; read_count is initialized to 0.
The semaphore rw_mutex is common to both reader and writer processes. The mutex semaphore is used to ensure mutual exclusion when the variable read count is updated. The read_count variable keeps track of how many processes are currently reading the object. The semaphore rw_mutex functions as a mutual exclusion semaphore for the writers. It is also used by the first or last reader that enters or exits the critical section. It is not used by readers who enter or exit while other readers are in their critical sections.
#include <thread> #include <mutex> #include <condition_variable> using namespace std; class Semaphore { public: Semaphore(int count_=0):count(count_){} inline void wait(){ std::unique_lock<std::mutex> lock(mtx); cv.wait(lock,[this](){return count > 0;}); count--; } inline void notify(){ std::unique_lock<std::mutex> lock(mtx); count++; cv.notify_one(); } private: std::mutex mtx; std::condition_variable cv; int count; }; Semaphore rw_mtx(1); // common to both reader and writer Semaphore mtx(1); // ensure mutual exclusion when variable read_count is updated int read_count=0; int value=0; // the value for readers and writers void reader(){ mtx.wait(); ++read_count; if (read_count==1) rw_mtx.wait(); mtx.notify(); cout << "reads " << value << '\n'; mtx.wait(); --read_count; if (read_count==0) rw_mtx.notify(); mtx.notify(); } void writer(int v){ rw_mtx.wait(); value = v; cout << "writes " << v << '\n'; rw_mtx.notify(); } int main(){ thread reader1(reader); thread reader2(reader); thread reader3(reader); thread writer1(writer,1); reader1.join(); reader2.join(); reader3.join(); writer1.join(); return 0; }
Reader-Writer Lock
The readers-writers problem and its solutions have been generalized to provide reader-writer locks on some systems. Acquiring a reader-writer lock requires specifying the mode of the lock: either read or write access. When a process wishes only to read shared data, it requests the reader-writer lock in read mode. A process wishing to modify the shared data must request the lock in write mode. Multiple processes are permitted to concurrently acquire a reader-writer lock in read mode, but only one process may acquire the lock for writing, as exclusive access is required for writers.
C++17 introduced a shared_mutex implementation that is now available in most C++ compilers.
While a regular mutex exposes 3 methods: lock
, unlock
and try_lock
, a shared_mutex adds 3 more: lock_shared
, unlock_shared
, try_lock_shared
. The first 3 methods work exactly the same as in a regular mutex. i.e. If a mutex is locked, all other threads will wait until it is unlocked. The shared versions are a little more complicated.
Allowing readers to always lock a shared mutex even if there are writers waiting could result in writes waiting forever, so the implementation of shared_mutex
uses a special algorithm that lets the programmer use it without having to think about this problem. There is one scenario that can be esily explained: If there is no thread calling lock
ever, all the threads calling lock_shared
will be allowed to proceed without ever having to wait.
#include <iostream> #include <thread> #include <shared_mutex> using namespace std; shared_mutex mtx; int value=0; // the value for readers and writers void reader(){ // shared_lock <=> mtx.lock_shared(), mtx.unlock_shared() shared_lock<shared_mutex> readLock(mtx); cout << "reads " << value << '\n'; } void writer(int v){ // shared_lock <=> mtx.lock(), mtx.unlock(), // here can also use lock_guard unique_lock<shared_mutex> writeLock(mtx); value = v; cout << "writes " << v << '\n'; } int main(){ thread reader1(reader); thread reader2(reader); thread reader3(reader); thread writer1(writer,1); reader1.join(); reader2.join(); reader3.join(); writer1.join(); return 0; }
Reference
Operating System Concepts Essentials, 2nd - Abraham Silberschatz
https://stackoverflow.com/questions/244316/reader-writer-locks-in-c
https://ncona.com/2019/03/read-write-mutex-with-shared_mutex/