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: lockunlock and try_lock, a shared_mutex adds 3 more: lock_sharedunlock_sharedtry_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/

posted @ 2019-10-26 04:44  約束の空  阅读(445)  评论(0编辑  收藏  举报