Solution 1, over-constrained: we have one lock (access) for
read and write.
Reader() {
while (true) {
lock(&access);
Read(database);
unlock(&access);
}
}
Writer() {
while (true) {
lock(&access);
Write(database);
unlock(&access);
}
}
Problem: only 1 reader at a time can access the database.
Solution 2: introduce a counter for the number of readers. The first reader locks the access to the database and the last one unlocks it. We need to protect the number of readers also.
Writer() { /* same as before */ }
Reader() {
while (true) {
lock(&read_mutex);
reader_nr ++;
if (reader_nr == 1)
lock(&access);
unlock(&read_mutex);
Read(database);
lock(&read_mutex);
reader_nr --;
if (reader_nr == 0) {
unlock(&access);
unlock(&read_mutex);
}
}
Problem: the writers may never get through.
Solution 3: also count the number of writers and the number of writers that are waiting. It works for the case where the number of readers is much higher than the number of writers, but the writers have priority over the readers - while there is any writer attempting to access the database, no reader can pass through.
Reader() {
while (true) {
good_to_go = 0; // Local variable !!
while (!good_to_go) {
lock(&mutex);
if (writer_nr == 0 &&
waiting
== 0) {
reader_nr ++;
good_to_go = 1;
}
unlock(&mutex);
}
Read(database);
lock(&mutex);
reader_nr --;
unlock(&mutex);
}
}
Writer() {
while (true) {
good_to_go = 0; // Local variable !!
signaled = 0; // Local variable
too.
while (!good_to_go) {
lock(&mutex);
if (reader_nr == 0 &&
writer_nr
== 0) {
writer_nr ++;
if (signaled)
waiting--;
good_to_go = 1;
}
else if (!signaled) {
signaled = 1;
waiting++;
}
unlock(&mutex);
}
Write(database);
lock(&mutex);
writer_nr --;
unlock(&mutex);
}
}
Example of code: reader_writer.cc