Skills101

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:

TimeThread AThread Bcount in memory
1reads 55
2reads 55
3writes 66
4writes 66 ❌

One update is lost. When the result depends on the order of execution, it's a race condition.

2. The critical section

Thread A acquires the lock and enters the critical section (balance += 100) while Thread B waits; the critical section needs mutual exclusion, progress and bounded waiting

The critical section is the code that touches shared data. A correct solution needs:

  1. Mutual exclusion — only one at a time inside.
  2. Progress — if nobody is inside, someone waiting must be allowed in (no needless blocking).
  3. 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.
TypeValuesUsed for
Binary semaphore0 or 1like a mutex
Counting semaphore0 … nn 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.

Practice what you learned

📝 Quiz: Process Synchronization5 questions · instant answers & explanations · +10 XP · counts towards your free certificateTake the quiz →