# Process Synchronization — Race Conditions, Mutex, Semaphores, Producer-Consumer

Source: https://skills101.in/learn/operating-systems/process-synchronization — Skills101 free tutorial (Operating Systems). Updated 2026-11-09.

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

![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](https://skills101.in/learn-img/operating-systems/critical-section.svg)

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.

```text
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`.

```text
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 quiz and free certificate: https://skills101.in/learn/operating-systems/process-synchronization