Operating Systems · Lesson 4 of 9 · Updated ·
Process Synchronization — Race Conditions, Mutex, Semaphores, Producer-Consumer
When two threads or processes use shared data at the same time, results can depend on timing — and be wrong. Synchronization makes sure they take turns safely.
1. A race condition
Two threads each run count = count + 1 once. Starting from 5, we expect 7. But count + 1 is really three steps — read, add, write:
| Time | Thread A | Thread B | count in memory |
|---|---|---|---|
| 1 | reads 5 | 5 | |
| 2 | reads 5 | 5 | |
| 3 | writes 6 | 6 | |
| 4 | writes 6 | 6 ❌ |
One update is lost. When the result depends on the order of execution, it's a race condition.
2. The critical section
The critical section is the code that touches shared data. A correct solution needs:
- Mutual exclusion — only one at a time inside.
- Progress — if nobody is inside, someone waiting must be allowed in (no needless blocking).
- Bounded waiting — nobody waits forever.
3. Mutex (lock)
A mutex is a lock with two operations: acquire() before the critical section, release() after. Only the thread holding it can enter.
acquire(lock)
count = count + 1 ← critical section
release(lock)
4. Semaphores
A semaphore is an integer with two atomic operations:
- wait(S) (also P / down): if S > 0, decrease it and continue; otherwise block.
- signal(S) (also V / up): increase S and wake a waiting process.
| Type | Values | Used for |
|---|---|---|
| Binary semaphore | 0 or 1 | like a mutex |
| Counting semaphore | 0 … n | n identical resources (e.g. 5 printers, 10 DB connections) |
Mutex vs semaphore (interview favourite): a mutex has an owner — only the thread that locked it can unlock it; it guards one resource. A semaphore is a counter — any thread can signal; it can manage many resources and is also used for signalling between threads.
5. Producer–consumer (bounded buffer)
A producer puts items into a buffer of size N; a consumer takes them out. Use three semaphores:
empty = N(free slots),full = 0(filled slots),mutex = 1.
Producer: Consumer:
wait(empty) wait(full)
wait(mutex) wait(mutex)
add item remove item
signal(mutex) signal(mutex)
signal(full) signal(empty)
The producer blocks when the buffer is full; the consumer blocks when it's empty. (Swapping the order of wait(empty) and wait(mutex) can cause a deadlock — a common trick question.)
6. Other classic problems
- Readers–writers: many readers may read together, but a writer needs exclusive access.
- Dining philosophers: 5 philosophers, 5 forks; each needs 2 forks. If all pick up their left fork at once → deadlock. Fixes: allow only 4 at the table, or make one philosopher pick the right fork first.
Common mistakes
- Thinking
count++is atomic. - Holding a lock while doing slow work (I/O) — it blocks everyone.
- Confusing mutex (ownership, one resource) with counting semaphore (count of resources).
Quick recap
- Race condition: the result depends on timing; protect shared data.
- Critical section needs mutual exclusion, progress, bounded waiting.
- Mutex: lock/unlock by the owner. Semaphore: wait/signal on a counter.
- Producer–consumer uses empty, full and mutex semaphores.