CIS 4307: Readers and Writers

There are a number of Classical Concurrency Problems, that is, of problems that allow us to examine in a simplified form typical problems that arise when we write concurrent programs. One of these problems is the Readers and Writers problem (other such problems are the Producers and Consumers problem, the Dining Philosophers problem, the Barbers problem, the Smokers problem, the Monkey Rock problem, ..). The Readers and Writers problem was originally proposed in [1]. There is a data structure, say S, a group of processes that can read from S, the Reader processes, and a group of processes that can write to S, the Writer processes. A writer process must operate in mutual exclusion with respect to all other processes. A reader process can operate concurrently with other readers. Here is a simple solution to the readers and writers problem:
        GLOBAL VARIABLES:
                mutex, db : semaphore := 1; // Mutual exclusion semaphores
                readcount : integer   := 0;
        READER:
                p(mutex);
                readcount++;
                if (readcount is 1) then p(db);
                v(mutex);
                CRITICAL REGION where we read
                p(mutex);
                readcount--;
                if (readcount  is 0) then v(db);
                v(mutex);
        WRITER:
                p(db);
                CRITICAL REGION where we write
                v(db);
Notice that in this solution on the semaphore db we will have at most one reader (all other readers will wait on mutex). But once a reader gets in, all waiting readers can (but not necessarily do [why?]) get in ahead of waiting writers. When a writer finishes, if there are waiting readers and writers, either readers or a writer will run.

Here is a paraphrase of the "classic" solution from [1]. In this solution Writers are given absolute priority over Readers.

        GLOBAL VARIABLES:
                mutex1 : semaphore := 1; // all semaphores here are mutual exclusion
                mutex2 : semaphore := 1;
                mutex3 : semaphore := 1;
                db     : semaphore := 1;
                r      : semaphore := 1;
                readcount, writecount : integer := 0;
        READER:
                p(mutex3);
                  p(r);
                    p(mutex1);
                    readcount++;
                    if (readcount is 1) then p(db);
                    v(mutex1)
                  v(r);
                v(mutex3);
                CRITICAL REGION where we read
                p(mutex1);
                readcount--;
                if (readcount is 0) then v(db);
                v(mutex1);
        WRITER:
                p(mutex2);
                writecount++;
                if (writecount is 1) then p(r);
                v(mutex2);
                p(db);
                CRITICAL REGION where we write
                v(db);
                p(mutex2);
                writecount--;
                if (writecount is 0) then v(r);
                v(mutex2);
In this solution: The two solutions we have seen differ only in the way they schedule readers and writers. This kind of scheduling established by the use of synchronization commands (here p and v on semaphores) is called intermediate scheduling as distinct from the microscheduling taking place at the ready queue and in the semaphores themselves, and from the macroscheduling taking place at the level of jobs.

Here is an additional solution, due to Kleiman,Shah, and Smaalders, using threads: rwl.h and rwl.c.

Notice that we have looked at implementations of the Readers and Writers problem starting from semaphores. In practice often systems provide as basic system primitives reader-and-writer locks, so that the user does not need to implement this kind of mechanism.

[1]P.J.Courtois,F.Heymans,D.L.Parnas: Concurrent Control with "Readers" and "Writers", CACM October 1971