← Operating System Concepts

BOOK NOTES · OPERATING SYSTEMS · CHAPTER 7

Operating System Concepts Chapter 7 — Synchronization Examples.

operating-systemschapter-7synchronizationproducer-consumerreaders-writersdining-philosophers

// the one-minute version

Three classic problems show how the Chapter 6 tools play out. Bounded buffer (producer–consumer): coordinate producers and consumers sharing a fixed queue. Readers–writers: let many readers share data but give writers exclusive access. Dining philosophers: the textbook deadlock trap, where grabbing resources in the wrong order locks everyone up. Each maps onto real systems, and the solutions teach the rules you'll actually use.

Chapter 6 handed you locks, semaphores, and monitors. This chapter is the worked examples — the three problems that, once you can solve them, mean you truly understand synchronization. They're not academic toys; each is a pattern you'll meet in real code.

01 The bounded-buffer (producer–consumer) problem

Producers create items and put them in a shared, fixed-size buffer; consumers take them out. The hazards: a producer must not add to a full buffer, a consumer must not take from an empty one, and they must not corrupt the buffer by touching it at once.

The clean solution uses three tools together: a counting semaphore for empty slots, one for full slots, and a mutex for mutual exclusion on the buffer. A producer waits on empty, locks the mutex, inserts, unlocks, signals full; a consumer waits on full, locks, removes, unlocks, signals empty. The two counting semaphores enforce the capacity limits; the mutex protects the structure.

think of it likeA small parcel locker between a delivery driver (producer) and residents (consumer). The driver can't stuff a full locker; a resident can't grab from an empty one. The "empty slots" and "filled slots" counts are exactly the two counting semaphores.
noteThis pattern is everywhere: thread pools (tasks in, workers out), logging pipelines, message queues, video buffering, OS print spools. "Producer–consumer over a bounded queue" is one of the most reused concurrency patterns in software — recognize it and the solution is ready-made.
watch outOrder matters in the producer/consumer code. If you grab the mutex before waiting on the counting semaphore, you can deadlock: a full-buffer producer holds the mutex and sleeps, so no consumer can ever lock the mutex to make room. Always wait on the capacity semaphore first, then take the mutex.

02 The readers–writers problem

Many threads want to read shared data; some want to write it. Multiple readers at once are fine — reading changes nothing. But a writer needs exclusive access: no other writer and no reader while it writes, or readers could see half-updated data.

Solutions trade off who gets priority. Reader-priority maximizes concurrency but can starve writers if readers keep arriving. Writer-priority keeps data fresh but can starve readers. Real systems use reader–writer locks that balance the two, and databases generalize this into shared vs exclusive locks.

watch outThe trap here is starvation, not corruption. A reader-priority lock with a constant stream of readers can leave a writer waiting forever — the data never updates. Choosing the priority policy is a real design decision, not a detail.

03 The dining-philosophers problem

Five philosophers sit around a table, one chopstick between each pair. To eat, a philosopher needs both neighboring chopsticks. The naive rule — "pick up your left, then your right" — causes deadlock: if all five grab their left at once, everyone holds one chopstick and waits forever for the right.

sharedtableP1P2P3P4P5each needs 2 forks

Fig 1 — Five philosophers, five shared chopsticks. "Everyone grabs left first" forms a circular wait — the textbook deadlock.

The fixes are exactly the deadlock-breaking strategies of Chapter 8: allow at most four philosophers at the table at once; require picking up both chopsticks together (atomically) or neither; or impose an ordering — number the chopsticks and always pick up the lower-numbered first. That last one breaks the circular wait and is the standard real-world fix.

key ideaDining philosophers is a deadlock lesson in costume. Its solution — acquire shared resources in a consistent global order — is the single most practical rule for avoiding deadlocks in real multithreaded code (e.g., always lock account A before account B by ID).

04 Why these three matter

Together they cover the core shapes of coordination: capacity limits (bounded buffer), shared-vs-exclusive access (readers–writers), and multi-resource deadlock (dining philosophers). Recognize which shape a real problem matches and the solution pattern follows. Almost every concurrency situation in production is one of these three wearing a different outfit.

common catches & gotchas

  • Lock-before-wait deadlock — In producer–consumer, taking the mutex before the capacity semaphore deadlocks. Wait on the semaphore first.
  • Reader starvation of writers — Naive reader-priority locks can block writers forever under steady reads. Pick the priority policy deliberately.
  • Grab-left-then-right — The intuitive philosopher solution is exactly the one that deadlocks. The ordering fix is counterintuitive but correct.
  • Counting semaphore vs mutex — The buffer needs both — two counting semaphores for capacity and a mutex for structure. Using one alone is wrong.
  • These aren't toys — If you don't recognize the pattern, you'll re-derive (badly) something the textbook already solved.

05 Questions students actually ask

Why does the bounded buffer need two semaphores plus a mutex?

The two counting semaphores track empty and full slots so producers/consumers block at the right times; the mutex stops them from corrupting the buffer by editing it simultaneously. Each tool solves a distinct part.

Why allow multiple readers at once?

Reading doesn't modify data, so concurrent readers can't corrupt anything or see inconsistent state. Forcing them to take turns would waste parallelism. Only writers need exclusivity.

What actually causes the dining-philosophers deadlock?

A circular wait: every philosopher holds one chopstick and waits for the next, with no one able to proceed. It satisfies all four deadlock conditions at once.

What's the most practical takeaway for real code?

Acquire shared locks in a consistent global order. If every thread locks resources in the same order (by ID, say), the circular wait that causes deadlock can't form.

How do these map to real systems?

Bounded buffer = thread pools and message queues; readers–writers = database and cache locking; dining philosophers = any code acquiring multiple locks, where lock ordering prevents deadlock.

06 Key takeaways

  • Bounded buffer: producers and consumers share a fixed queue — empty/full counting semaphores plus a mutex (wait on the semaphore before the mutex).
  • Readers–writers: many readers concurrently, writers exclusively; watch for starvation.
  • Dining philosophers: inconsistent acquisition order causes deadlock.
  • The philosophers' fix — a consistent acquisition order — is the top real-world anti-deadlock rule.
  • These three patterns (capacity, shared/exclusive, multi-resource) cover most real coordination problems.

07 Wrapping up

The dining philosophers walked us right up to deadlock. The next chapter takes it head-on: what deadlock is, and the four ways to deal with it. Next up: Deadlocks.

← prev: Chapter 6next: Chapter 8 →
© cvam — written in plaintext, served warm