OS · Concurrency

Concurrency & Synchronization

Race conditions, critical sections, mutexes and semaphores — why tests pass but production corrupts data.

Operating Systems Basics → Interview
01

The Core Problem

When two threads touch shared data and at least one writes, the interleaving of their operations decides the result — a race condition. The classic example: two threads each run count++, which is really read → add → write. Interleave them and one update is lost.

The lost update
count = 0            // shared

Thread A: read 0 → add 1 → write 1
Thread B: read 0 → add 1 → write 1   // A's update lost!

// Expected 2, got 1.
A critical section is code that touches shared state and must run without interference. The whole game is making critical sections mutually exclusive.
02

Mutexes & Semaphores

PrimitiveWhat it isUse for
MutexA lock — one holder at a timeProtecting a critical section
Binary semaphore0/1 counter with wait/signalSimilar to a mutex, no ownership
Counting semaphoreCounter ≥ 0Limiting N concurrent users of a resource
Condition variableWait until a predicate holdsProducer/consumer signaling
Mutex-guarded critical section
lock.acquire()
try:
    count += 1     # critical section
finally:
    lock.release()
03

Rules of a Correct Solution

A correct lock guarantees
  • Mutual exclusion — one thread in the critical section
Common mistakes
  • Forgetting to release the lock (leak → deadlock)

Any mutual-exclusion solution must provide three properties: mutual exclusion (at most one in the critical section), progress (if it’s free, someone waiting gets in), and bounded waiting (no one waits forever).

04

Producer–Consumer

The canonical synchronization problem: producers add items to a bounded buffer, consumers remove them. You need a mutex for the buffer plus two counting semaphoresempty slots and full slots — so producers block when full and consumers block when empty.

Producer / Consumer (semaphores)
empty = Semaphore(N)   # free slots
full  = Semaphore(0)   # filled slots
mutex = Lock()

# Producer
empty.acquire(); mutex.acquire()
buffer.put(item)
mutex.release(); full.release()

# Consumer
full.acquire();  mutex.acquire()
item = buffer.get()
mutex.release(); empty.release()
05

Interview Questions

What is a race condition?

A bug where the result depends on the nondeterministic timing of threads accessing shared data, with at least one writer. Fix by making the critical section mutually exclusive.

Mutex vs semaphore?

A mutex is a lock with ownership — only the thread that locked it should unlock it — used for mutual exclusion. A semaphore is a signaling counter with no ownership; a counting semaphore limits N concurrent accesses.

Why is count++ not atomic?

It compiles to read-modify-write (load, increment, store). A thread can be preempted between those steps, so two threads can both read the old value and one increment is lost.

What three properties must a mutual-exclusion solution guarantee?

Mutual exclusion, progress, and bounded waiting.

Quick Quiz

1. A race condition requires at least one thread to…
2. Which primitive has the notion of an owner?
3. To limit 5 concurrent users of a resource, use a…
4. count++ can lose updates because it is…
5. Producer/consumer needs how many counting semaphores?