Three classic puzzles — dining philosophers, producer-consumer, readers-writers — and the semaphore rules that solve them.
Operating-systems courses lean on three classic puzzles because each one distils a real concurrency hazard into a tiny, memorable story. Dining philosophers shows how shared resources can deadlock. Producer-consumer shows how to coordinate a fixed-size shared buffer so it never overflows or is read empty. Readers-writers shows shared-versus-exclusive access. Each is solved with the right semaphores plus one rule that rules out the bad interleaving.
semaphore empty = N; // count of free slots
semaphore full = 0; // count of filled slots
semaphore mutex = 1; // lock for the buffer
// Producer // Consumer
wait(empty); wait(full);
wait(mutex); wait(mutex);
buffer[in] = item; item = buffer[out];
in = (in + 1) % N; out = (out + 1) % N;
signal(mutex); signal(mutex);
signal(full); signal(empty);empty and full count the slots; mutex protects the buffer indices. A producer waits for a free slot (empty), takes the lock, inserts an item, then signals that one more slot is full. The consumer mirrors it: wait for a filled slot, take the lock, remove an item, then signal a slot is empty again.
wait(mutex) *before* wait(empty), it could grab the lock and then block on a full buffer — holding the very lock the consumer needs to drain it. Always acquire the counting semaphore *before* the mutex.