Process Synchronisation for GATE CS
Critical section, race condition, mutex, semaphore, monitors, Dining Philosophers, Readers-Writers — complete GATE CS coverage with code tracing.
Last updated: April 2026 | GATE CS 2024–2026 syllabus
Key Takeaways for GATE
- Critical section problem requires: Mutual Exclusion + Progress + Bounded Waiting.
- Race condition: outcome depends on execution order of concurrent processes — must be prevented.
- Semaphore: integer variable with atomic wait() (P) and signal() (V) operations. Counting semaphore can be any non-negative integer; binary semaphore is 0 or 1.
- wait(S): if S>0, decrement S; else block. signal(S): if blocked processes, wake one; else increment S.
- Mutex has ownership — only locker can unlock. Binary semaphore has no ownership.
- Monitor: high-level synchronisation construct with mutual exclusion built-in. Uses condition variables (wait/signal).
- Peterson’s solution works for 2 processes but requires busy waiting (spin lock).
1. Critical Section Problem
Three requirements for a valid solution:
1. Mutual Exclusion: Only one process in critical section at a time.
2. Progress: If no process is in CS and processes want to enter, selection cannot be postponed indefinitely. Processes not in remainder section participate in decision.
3. Bounded Waiting: After a process requests entry, there is a bound on how many times others can enter before it does (no starvation).
Structure of a process:
do {
entry section // request permission
critical section // access shared resource
exit section // release permission
remainder section // other code
} while (true);2. Race Condition
Classic example — shared counter:
// Shared: int count = 5; // P1: count++ (read count=5, compute 6, write 6) // P2: count-- (read count=5, compute 4, write 4) // If both read before either writes: final count = 4 or 6, NOT 5!
The correct value (5) is lost. This is a race condition.
At machine instruction level, count++ is three steps:
register = count; register = register + 1; count = register.
Context switch between any two steps causes incorrect result.
3. Peterson’s Solution (2-process)
turn (whose turn to enter) and flag[2] (who wants to enter).// Process Pi (j = 1-i): flag[i] = true; turn = j; // give other process chance while (flag[j] && turn == j); // busy wait // critical section flag[i] = false; // remainder section
Satisfies all three requirements. Works for exactly 2 processes.
Limitation: Busy waiting (spin lock) — wastes CPU. Not scalable to n processes directly.
4. Semaphores
wait(S) / P(S):
while (S <= 0); // busy wait (or block) S--;
signal(S) / V(S):
S++;
Types:
Binary semaphore: S ∈ {0, 1}. Equivalent to mutex (but no ownership).
Counting semaphore: S ∈ {0, 1, 2, …}. Used to control access to a pool of n resources.
Blocking implementation (no busy wait):
wait: if S≤0, add process to waiting queue and block. signal: if queue non-empty, wake one process; else S++.
Semaphore for mutual exclusion: init S=1. Enclose CS with wait(S) … signal(S).
Semaphore for ordering: init S=0. P2 does wait(S); P1 signals after completing its task.
5. Mutex Locks
Operations: acquire() and release(), both atomic.
Key difference from semaphore: Mutex has ownership — only the thread that called acquire() can call release(). Binary semaphore allows any thread to signal.
Spinlock: Mutex with busy waiting. Good for short critical sections on multicore (no context switch overhead). Bad for long waits or uniprocessor.
Priority inversion: Low-priority process holds mutex needed by high-priority process — high priority is effectively blocked by low priority. Solution: Priority inheritance protocol.
6. Monitors
Condition variables: x.wait() — suspends calling process. x.signal() — resumes one suspended process.
Unlike semaphore signal, monitor signal() has no effect if no process is waiting (signal is lost).
Hoare monitor: signalling process waits while signalled process runs.
Mesa monitor (used in practice): signalling process continues; signalled process goes to ready queue and re-checks condition.
Mesa monitors require while loop (not if) around condition check.
7. Producer-Consumer (Bounded Buffer)
Semaphore solution:
Semaphore mutex = 1; // mutual exclusion on buffer Semaphore empty = n; // empty slots (init = n) Semaphore full = 0; // filled slots (init = 0) Producer: Consumer: wait(empty); wait(full); wait(mutex); wait(mutex); add item to buffer; remove item from buffer; signal(mutex); signal(mutex); signal(full); signal(empty);
Order matters: wait(empty/full) must come BEFORE wait(mutex) — reversing causes deadlock.
8. Readers-Writers Problem
First Readers-Writers (readers-preference):
Semaphore rw_mutex = 1; // writer/reader-writer exclusion Semaphore mutex = 1; // protect read_count int read_count = 0; Reader: Writer: wait(mutex); wait(rw_mutex); read_count++; write; if (read_count==1) wait(rw_mutex);signal(rw_mutex); signal(mutex); read; wait(mutex); read_count--; if (read_count==0) signal(rw_mutex); signal(mutex);
Problem: writers may starve if readers keep arriving.
Second Readers-Writers (writers-preference): New readers blocked if a writer is waiting. Prevents writer starvation but may starve readers.
9. Dining Philosophers Problem
Naive solution causes deadlock: Each picks left fork → all wait for right fork → circular wait.
Solutions:
1. Allow at most 4 philosophers to sit simultaneously (1 always blocked).
2. Pick both forks atomically (requires mutex on the pair).
3. Odd philosophers pick left-then-right; even pick right-then-left (breaks symmetry).
4. Use semaphore array: state[] = {THINKING, HUNGRY, EATING}; test() checks neighbours.
Semaphore solution:
Semaphore fork[5] = {1,1,1,1,1};
Philosopher i:
wait(fork[i]);
wait(fork[(i+1) % 5]);
eat;
signal(fork[i]);
signal(fork[(i+1) % 5]);This causes deadlock! Fix: philosopher 4 picks right fork first.
10. GATE Examples
// Shared: S=1 // P1: wait(S); CS1; signal(S) // P2: wait(S); CS2; signal(S) // Interleaving: P1 wait(S)→S=0; P2 wait(S)→blocked; P1 CS1; P1 signal(S)→wakes P2; P2 CS2
Mutual exclusion maintained. Only one in CS at a time. ✓
// Process P1: while(true) { CS; } // never leaves CS
// Process P2: entry section; while(turn != 2); // waits foreverP1 never exits CS — P2 waits indefinitely. Bounded Waiting is violated.
// Bug: wait(mutex) before wait(empty) Producer: wait(mutex); wait(empty); ... Consumer: wait(mutex); wait(full); ...
If buffer full: Producer holds mutex, waits on empty. Consumer waits on mutex. Deadlock!
Fix: always acquire resource semaphore (empty/full) before mutex.
11. Common Mistakes
- Reversing wait order in Producer-Consumer: Always do wait(empty/full) before wait(mutex). Doing wait(mutex) first can cause deadlock when buffer is full or empty.
- Confusing signal() in monitors vs semaphores: Semaphore signal() always increments and is never lost. Monitor signal() wakes a waiting process — if none waiting, the signal is lost (no increment).
- Busy waiting vs blocking semaphore: Busy wait (spin) wastes CPU on uniprocessor — the blocked process keeps checking. Blocking semaphore puts the process to sleep. GATE questions assume blocking unless stated.
- Mutual exclusion alone is not enough: A solution with only mutual exclusion may still deadlock or starve. All three conditions (ME + Progress + BW) must be satisfied.
- Peterson’s solution on modern hardware: May not work without memory barriers due to instruction reordering by modern CPUs. GATE assumes sequential consistency.
12. FAQ
- What are the three conditions for the critical section problem?
- Mutual Exclusion: at most one process in the critical section at any time. Progress: if no process is in the CS and processes want to enter, selection cannot be postponed indefinitely — processes not in their remainder section participate in the entry decision. Bounded Waiting: after a process requests entry, there is an upper bound on how many times other processes can enter before it does (prevents starvation).
- What is the difference between a mutex and a binary semaphore?
- Ownership is the key difference. A mutex lock must be released by the same thread that acquired it — this prevents one thread from accidentally releasing another thread’s lock. A binary semaphore (value 0 or 1) has no ownership — any thread can signal it. This makes binary semaphores suitable for signalling between threads (e.g., one thread signals another to proceed), while mutex is correct for mutual exclusion.
- What is a race condition in operating systems?
- A race condition occurs when the correctness of a program depends on the relative timing/order of concurrent operations accessing shared data. Since thread/process scheduling is non-deterministic, the outcome is unpredictable. Classic example: two threads both do count++ — if they both read the value before either writes back, one increment is silently lost. Race conditions are prevented by synchronisation mechanisms (mutex, semaphore, monitors).
- How does a counting semaphore solve the producer-consumer problem?
- Three semaphores: mutex=1 (mutual exclusion on buffer), empty=n (counts empty slots), full=0 (counts filled slots). Producer calls wait(empty) before adding (blocks if buffer full), then wait(mutex) for exclusive buffer access, adds item, signals mutex then full. Consumer calls wait(full) before removing (blocks if buffer empty), then wait(mutex), removes item, signals mutex then empty. The ordering (resource semaphore before mutex) prevents deadlock.