kimsay
kimsay

Reputation: 321

readers-/writers multithreading in C++11

I'm trying to implement a readers writers solution in C++ with std::thread.

I create several reader-threads that run in an infinite loop, pausing for some time in between each read access. I tried to recreate the algorithm presented in Tanenbaum's book of Operating Systems:

    rc_mtx.lock(); // lock for incrementing readcount
    read_count += 1;
    if (read_count == 1) // if this is the first reader
        db_mtx.lock(); // then make a lock on the database
    rc_mtx.unlock();

    cell_value = data_base[cell_number]; // read data from database
    rc_mtx.lock();
    read_count -= 1; // when finished 'sign this reader off'
    if (read_count == 0) // if this was the last one
        db_mtx.unlock(); // release the lock on the database mutex
    rc_mtx.unlock();

Of course, the problem is that the thread who might satisfy the condition of being the last reader (and therefore want to do the unlock) has never acquired the db_mtx. I tried to open another 'mother' thread for the readers to take care of acquiring and releasing the mutex, but I go lost during the process. If there is an elegant way to overcome this issue (thread might try to release a mutex that has never been acquired) in an elegant way I'd love to hear!

Upvotes: 1

Views: 2184

Answers (1)

jxh
jxh

Reputation: 70382

You can use a condition variable to pause writers if readers are in progress, instead of using a separate lock.

// --- read code
rw_mtx.lock();    // will block if there is a write in progress
read_count += 1;  // announce intention to read
rw_mtx.unlock();
cell_value = data_base[cell_number];
rw_mtx.lock();
read_count -= 1;  // announce intention to read
if (read_count == 0) rw_write_q.notify_one();
rw_mtx.unlock();

// --- write code
std::unique_lock<std::mutex> rw_lock(rw_mtx);
write_count += 1;
rw_write_q.wait(rw_lock, []{return read_count == 0;});
data_base[cell_number] = cell_value;
write_count -= 1;
if (write_count > 0) rw_write_q.notify_one();

This implementation has a fairness issue, because new readers can cut in front of waiting writers. A completely fair implementation would probably involve a proper queue that would allow new readers to wait behind waiting writers, and new writers to wait behind any waiting readers.

In C++14, you can use a shared_timed_mutex instead of mutex to achieve multiple readers/single writer access.

// --- read code
std::shared_lock<std::shared_timed_mutex> read_lock(rw_mtx);
cell_value = data_base[cell_number];

// --- write code
std::unique_lock<std::shared_timed_mutex> write_lock(rw_mtx);
data_base[cell_number] = cell_value;

There will likely be a plain shared_mutex implementation in the next C++ standard (probably C++17).

Upvotes: 4

Related Questions