Introduction
Three classic problems (Silberschatz 7.1) are the standard test bed for any new synchronization primitive. The bounded-buffer (producer-consumer) problem is about counting and waiting for space or data. The readers-writers problem is about letting many readers share while writers get exclusive access, and about who gets starved. The dining philosophers problem is about acquiring several resources at once without deadlock or starvation.
The standard semaphore solution of each problem works for the reasons given in its section; small changes break it, such as swapping two wait calls or letting every philosopher pick up the same side first, and the readers-writers section adds the priority variants.
Prerequisites
- Semaphores, Mutexes and Monitors:
waitandsignal, initial values 1, 0 and , monitors with condition variables.
The bounded-buffer problem
Producers put items into a buffer of slots; consumers take them out. A producer must wait when the buffer is full, a consumer when it is empty, and buffer updates must be mutually exclusive.
Before starting any producer or consumer, require n >= 1, allocate buffer[0..n-1], and initialize in <- 0 and out <- 0; retain the semaphore initial values below.
semaphores: mutex <- 1 (protects the buffer and its indices)
empty <- n (free slots)
full <- 0 (filled slots)
PRODUCER: CONSUMER:
loop loop
produce item wait(full)
wait(empty) wait(mutex)
wait(mutex) item <- buffer[out]; out <- (out + 1) mod n
buffer[in] <- item signal(mutex)
in <- (in + 1) mod n signal(empty)
signal(mutex) consume item
signal(full)Invariant. At every moment, empty + full equals minus the number of producers and consumers between their first wait and their final signal. When nobody is in the middle of an operation, empty + full and full is the number of items. All slots can be used, unlike the busy-waiting in/out buffer of Inter-Process Communication, which can fill only .

IMPERATIVE RULE. Wait on the counting semaphore (empty or full) before mutex, never after. If a producer runs wait(mutex) first and then blocks on wait(empty) because the buffer is full, it sleeps while holding mutex; no consumer can ever enter to make space, and the system deadlocks. The order of the two signal calls does not matter for correctness.
With several producers and consumers the same code works unchanged, because mutex serializes all index updates.
The readers-writers problem
Many processes read a shared object; some write it. Any number of readers may read together, but a writer needs exclusive access (no other writer, no reader).
First readers-writers solution (readers preference)
semaphores: rw_mutex <- 1 (held by a writer, or by the group of readers)
mutex <- 1 (protects read_count)
integer: read_count <- 0
WRITER: READER:
wait(rw_mutex) wait(mutex)
write read_count <- read_count + 1
signal(rw_mutex) if read_count = 1 then wait(rw_mutex) -- first reader locks writers out
signal(mutex)
read
wait(mutex)
read_count <- read_count - 1
if read_count = 0 then signal(rw_mutex) -- last reader lets writers in
signal(mutex)- The first reader to arrive locks
rw_mutexon behalf of all readers; the last to leave releases it. - If a writer holds
rw_mutex, the first reader blocks on it while holdingmutex, so all later readers queue onmutex. When the writer leaves, they all go in. - Writers can starve. As long as readers keep arriving while at least one reader is inside,
read_countnever returns to 0 and a waiting writer never getsrw_mutex.
Second readers-writers problem (writers preference)
Once a writer is waiting, no new reader may start. This needs an extra semaphore that readers must pass (often called read_try) and a writer count; the first waiting writer locks readers out. Now readers can starve under a stream of writers. A fair solution queues readers and writers in arrival order.
| Variant | Who waits for whom | Who can starve |
|---|---|---|
| First (readers preference) | A writer waits until no reader is active | Writers |
| Second (writers preference) | New readers wait while any writer waits | Readers |
| Fair (FIFO queue) | Arrival order | Nobody |
Readers-writers locks. Many systems provide them directly (POSIX pthread_rwlock_t). They pay off when reads far outnumber writes; otherwise the bookkeeping costs more than a plain mutex.
The dining philosophers problem
Five philosophers sit round a table with one chopstick between each pair (five in all). A philosopher alternates thinking and eating, and needs both neighbouring chopsticks to eat.
semaphores: chopstick[0..4] <- 1
PHILOSOPHER i:
loop
think
wait(chopstick[i]) -- left
wait(chopstick[(i + 1) mod 5]) -- right
eat
signal(chopstick[(i + 1) mod 5])
signal(chopstick[i])Deadlock. If all five pick up their left chopstick at once, every right chopstick is taken and all five wait forever. All four Coffman conditions hold: chopsticks are exclusive, each holds one and waits for one, none can be taken away, and the waits form a cycle.
Remedies (each breaks one condition):
| Remedy | How | Condition broken |
|---|---|---|
| At most four at the table | A counting semaphore room <- 4 taken before the chopsticks | Circular wait (one philosopher always gets both) |
| Asymmetric order | Odd philosophers take left first, even take right first (or just one philosopher reverses) | Circular wait |
| Both or none | Pick up both chopsticks inside a critical section, or with a monitor that checks both neighbours | Hold and wait |
Deadlock-free is not starvation-free. In the monitor solution (a philosopher eats only if neither neighbour is eating), two neighbours can alternate so that the philosopher between them never finds both free. Preventing starvation needs extra fairness, such as a queue or priorities.
How many can eat at once? With five chopsticks and two per meal, at most philosophers eat simultaneously, and they cannot be neighbours.
Worked examples
Which order deadlocks the bounded buffer?
With , one producer making 3 items and one consumer taking 3: the standard order (counting semaphore first) never deadlocks and never puts the item count outside 0 to 1. Swapping the producer's two waits allows this sequence: the producer fills the slot, loops, takes mutex, blocks on empty (still 0); the consumer blocks on mutex. Deadlock. The same check with , four items, or with two producers and two consumers, finds no error in the standard order.
Readers and writers
With two readers and one writer under the first solution: both readers can be reading at the same time, a reader and the writer are never active together, and two writers are never active together. Suppose reader A is reading, so read_count and rw_mutex , and a writer is blocked on rw_mutex. A new reader B passes mutex, makes read_count , skips wait(rw_mutex) and starts reading. If readers keep overlapping like this, the writer never runs.
Philosophers
For 3, 4 and 5 philosophers, the plain left-then-right program can deadlock, while the asymmetric version (the last philosopher takes right first) and the version with a room semaphore of cannot.
Semaphore values along one run
A five-slot buffer () starts empty. The producer tries to put in 7 items before the consumer starts; then the consumer takes 3 items while the producer finishes. Blocking semaphores are used, so a wait decrements first and a negative value counts the blocked processes. The mutex operations always succeed here and are left out.
| Step | Event | empty | full | Items in buffer |
|---|---|---|---|---|
| 1 to 5 | Producer adds items 1 to 5 | 0 | 5 | 5 |
| 6 | Producer wait(empty) for item 6: blocks | 5 | 5 | |
| 7 | Consumer wait(full), takes item 1, signal(empty) wakes the producer | 0 | 4 | 4 |
| 8 | Producer stores item 6, signal(full) | 0 | 5 | 5 |
| 9 | Consumer takes item 2 | 1 | 4 | 4 |
| 10 | Producer wait(empty), stores item 7, signal(full) | 0 | 5 | 5 |
| 11 | Consumer takes item 3 | 1 | 4 | 4 |
At the end, 7 items were produced and 3 consumed, so full and empty , whatever the interleaving, as the invariant requires once nobody is inside an operation. The at step 6 appears only with blocking semaphores; with the busy-waiting definition the producer spins at empty = 0 and the value never goes negative.
Important properties and observations
- The bounded-buffer solution uses each kind of semaphore pattern once:
mutexfor exclusion,emptyandfullfor counting and waiting. - In readers-writers,
rw_mutexis acquired by one reader and may be released by another (the last one): this is exactly why it must be a semaphore, not a mutex with ownership. - Every dining-philosophers fix works by breaking one of the four deadlock conditions; see Deadlock.
- Starvation analysis is separate from deadlock analysis in all three problems.
Common misconceptions.
- "Swapping the two
signalcalls in the producer causes deadlock." Only the order of thewaitcalls matters. - "The first readers-writers solution is starvation-free." Writers can starve.
- "Limiting the table to four philosophers prevents starvation." It prevents deadlock; fairness depends on the semaphore's wake-up order.
GATE examination perspective
- Spot the deadlock. In producer-consumer variants, look for a
wait(mutex)followed by awaiton a counting semaphore that can be 0. - Initial values.
empty,full,mutex; swapped initial values block everybody or allow overflow. - Readers-writers. Which process locks and unlocks
rw_mutex(first and last reader); which side starves in each variant. - Dining philosophers. Deadlock in the symmetric program; fixes by asymmetry, by seats, or by taking both at once; maximum simultaneous eaters .
- Counting values. Given a sequence of operations, track
empty,fulland the number of items; the sumempty + fullreturns to when no operation is in progress.
Summary
Sources
- A. Silberschatz, P. B. Galvin, G. Gagne, Operating System Concepts, 10th ed., Wiley: Chapter 7, Section 7.1 Classic Problems of Synchronization (bounded buffer, readers-writers, dining philosophers).
- A. S. Tanenbaum, H. Bos, Modern Operating Systems, 4th ed., Pearson: Section 2.3.5 Semaphores (producer-consumer with semaphores), Section 2.5 Classical IPC Problems (2.5.1 The Dining Philosophers Problem, 2.5.2 The Readers and Writers Problem).