I'm a little late to this question, but I believe I have some pertinent information.
The proposals of shared_mutex
to the C++ committee, which the boost libs are based on, purposefully did not specify an API to give readers nor writers priority. This is because Alexander Terekhov proposed an algorithm about a decade ago that is completely fair. It lets the operating system decide whether the next thread to get the mutex is a reader or writer, and the operating system is completely ignorant as to whether the next thread is a reader or writer.
Because of this algorithm, the need for specifying whether a reader or writer is preferred disappears. To the best of my knowledge, the boost libs are now (boost 1.52) implemented with this fair algorithm.
The Terekhov algorithm consists of having the read/write mutex consist of two gates: gate1 and gate2. Only one thread at a time can pass through each gate. The gates can be implemented with a mutex and two condition variables.
Both readers and writers attempt to pass through gate1. In order to pass through gate1 it must be true that a writer thread is not currently inside of gate1. If there is, the thread attempting to pass through gate1 blocks.
Once a reader thread passes through gate1 it has read ownership of the mutex.
When a writer thread passes through gate1 it must also pass through gate2 before obtaining write ownership of the mutex. It can not pass through gate2 until the number of readers inside of gate1 drops to zero.
This is a fair algorithm because when there are only 0 or more readers inside of gate1, it is up to the OS as to whether the next thread to get inside of gate1 is a reader or writer. A writer becomes "prioritized" only after it has passed through gate1, and is thus next in line to obtain ownership of the mutex.
I used your example compiled against an example implementation of what eventually became shared_timed_mutex
in C++14 (with minor modifications to your example). The code below calls it shared_mutex
which is the name it had when it was proposed.
I got the following outputs (all with the same executable):
Sometimes:
Worked finished her work
Worked finished her work
Grabbed exclusive lock, killing system
KILLING ALL WORK
Workers are on strike. This worker refuses to work
Workers are on strike. This worker refuses to work
And sometimes:
Worked finished her work
Grabbed exclusive lock, killing system
KILLING ALL WORK
Workers are on strike. This worker refuses to work
Workers are on strike. This worker refuses to work
Workers are on strike. This worker refuses to work
And sometimes:
Worked finished her work
Worked finished her work
Worked finished her work
Worked finished her work
Grabbed exclusive lock, killing system
KILLING ALL WORK
I believe it should be theoretically possible to also obtain other outputs, though I did not confirm that experimentally.
In the interest of full disclosure, here is the exact code I executed:
#include "../mutexes/shared_mutex"
#include <thread>
#include <chrono>
#include <iostream>
using namespace std;
using namespace ting;
mutex outLock;
shared_mutex workerAccess;
bool shouldIWork = true;
class WorkerKiller
{
public:
void operator()()
{
unique_lock<shared_mutex> lock(workerAccess);
cout << "Grabbed exclusive lock, killing system" << endl;
this_thread::sleep_for(chrono::seconds(2));
shouldIWork = false;
cout << "KILLING ALL WORK" << endl;
}
private:
};
class Worker
{
public:
Worker()
{
}
void operator()()
{
shared_lock<shared_mutex> lock(workerAccess);
if (!shouldIWork) {
lock_guard<mutex> _(outLock);
cout << "Workers are on strike. This worker refuses to work" << endl;
} else {
this_thread::sleep_for(chrono::seconds(1));
lock_guard<mutex> _(outLock);
cout << "Worked finished her work" << endl;
}
}
};
int main()
{
Worker w1;
Worker w2;
Worker w3;
Worker w4;
WorkerKiller wk;
thread workerThread1(w1);
thread workerThread2(w2);
thread workerKillerThread(wk);
thread workerThread3(w3);
thread workerThread4(w4);
workerThread1.join();
workerThread2.join();
workerKillerThread.join();
workerThread3.join();
workerThread4.join();
return 0;
}