By the end of this chapter you'll be able to…

  • 1State mutual exclusion, progress and bounded waiting precisely and identify which one a flawed solution violates
  • 2Explain why Peterson's algorithm sets turn to the other thread and prove all three properties hold
  • 3Compare test-and-set with compare-and-swap and explain why plain test-and-set lacks bounded waiting
  • 4Use semaphores correctly in the bounded buffer and exhibit the deadlock caused by swapping the waits
  • 5Explain why a Mesa-semantics condition variable requires a while loop rather than an if
  • 6Describe the starvation failure in naive readers-writers and the trade-off in each fix
  • 7Map each dining-philosophers fix onto the deadlock condition it denies
  • 8State the four necessary conditions for deadlock and name a prevention technique for each
  • 9Run the banker's algorithm to find a safe sequence and decide a request
  • 10Apply the minimum-resource formula and explain why a cycle is sufficient only with single-instance resources
💡
Why this chapter matters in GATE
Every concurrency bug is a claim about an interleaving, and every fix restricts which interleavings can occur, so the whole topic becomes tractable once you learn to argue by exhibiting an ordering. GATE tests which of the three critical-section requirements a given attempt violates, semaphore ordering deadlocks, safe-state computation, and the minimum-resource formula.

Before you start — revise these

🔗
Processes and threads, and what is shared between threads of one process
🔗
Preemption: that a thread can be interrupted between any two instructions
🔗
Basic matrix bookkeeping for the allocation and need tables

Concurrency, Synchronization & Deadlock

Concurrency introduces a class of bug that testing cannot reliably find, because the failure depends on timing rather than on input.

The organising fact is that every concurrency bug is a claim about an interleaving, and every solution is a restriction on which interleavings the system may produce. A race condition is the assertion that some ordering of instructions from different executions produces a wrong result. A lock is the assertion that certain orderings are now impossible.

This reframing is what makes the topic tractable. To show code is broken, exhibit one bad interleaving. To show it is correct, argue that no bad interleaving remains.

The second organising fact is that deadlock is not a bug in any single process. Each participant is following a perfectly reasonable rule; the failure is a property of the set. That is why deadlock is analysed with graphs and matrices rather than by reading code.

The third is that all four necessary conditions must hold simultaneously, so every prevention technique works by denying exactly one of them.

1. The Critical Section Problem

A critical section is a region of code that accesses shared data and must not be executed by two threads at once.

Any correct solution must satisfy three requirements, and questions frequently ask which one a given attempt violates.

Mutual exclusion: at most one thread is inside the critical section at any time.

Progress: if no thread is in the critical section and some threads want to enter, the choice of who enters cannot be postponed indefinitely, and threads not wanting to enter cannot participate in the decision.

Bounded waiting: there is a bound on how many times other threads may enter after a thread has requested entry and before that request is granted.

Mutual exclusion alone is easy and worthless. A solution that lets nobody in satisfies it perfectly. Progress is what rules out that degenerate answer, and bounded waiting is what rules out starvation.

A fourth property, often listed separately, is that no assumption may be made about relative speeds of the threads or the number of processors.

2. Software and Hardware Solutions

Peterson's algorithm solves the problem for two threads using two shared variables: a boolean flag per thread signalling intent, and a turn variable breaking ties.

A thread sets its own flag, sets turn to the other thread, and then waits while the other's flag is set and it is the other's turn.

Setting turn to the other thread is the counterintuitive step that makes it work. Each thread politely yields, so whichever writes turn last loses the tie, and exactly one proceeds.

Peterson's algorithm satisfies all three requirements but assumes sequentially consistent memory. On real processors with write reordering it needs memory barriers, which is why it is a teaching device rather than an implementation.

Hardware provides atomic instructions that do a read and a write indivisibly.

Test-and-set returns the old value of a memory word and sets it to true, atomically. A lock is acquired by spinning until test-and-set returns false.

Compare-and-swap takes an expected value and a new value, and writes the new value only if the current value matches the expected one, returning what it found. It is strictly more powerful and is the basis of lock-free data structures.

Plain test-and-set locks do not guarantee bounded waiting, since an unlucky thread can lose every race forever. Adding a waiting array and a round-robin handoff restores it.

Spinning wastes CPU, which is acceptable only when the expected wait is shorter than a context switch. Otherwise the thread should block.

3. Semaphores

A semaphore is an integer with two atomic operations and a queue of blocked threads.

Wait, also called P or down, decrements the value and blocks if the result is negative. Signal, also called V or up, increments the value and wakes a blocked thread if any are waiting.

A binary semaphore takes values 0 and 1 and acts as a lock. A counting semaphore holds a resource count and admits that many holders.

When the value is negative, its magnitude is the number of blocked threads, which is a useful invariant for solving numerical questions.

Semaphores are powerful and error-prone in equal measure. Omitting a signal deadlocks; omitting a wait breaks mutual exclusion; reversing two waits can deadlock even though both are present.

The bounded buffer uses three semaphores: a counting semaphore for empty slots initialised to the buffer size, a counting semaphore for full slots initialised to zero, and a binary mutex.

The order of the two waits in the producer is not a style choice. Waiting on the mutex before waiting on empty slots allows a producer to hold the mutex while blocking on a full buffer, which prevents any consumer from ever draining it.

4. Monitors and Condition Variables

A monitor packages shared data with the procedures that operate on it, and guarantees that at most one thread is active inside the monitor at a time.

Mutual exclusion becomes structural rather than a matter of discipline, which removes the most common semaphore error.

Condition variables handle waiting. A thread that cannot proceed calls wait on a condition variable, which releases the monitor and blocks. Another thread calls signal to wake it.

A condition variable has no value and no memory. Signalling when nobody is waiting does nothing, unlike a semaphore signal which is remembered in the count.

Two signalling disciplines exist. Under signal-and-wait, sometimes called Hoare semantics, the signaller yields the monitor immediately to the woken thread. Under signal-and-continue, or Mesa semantics, the signaller keeps the monitor and the woken thread merely becomes eligible.

Mesa semantics is what real systems implement, and it forces a specific coding rule: always re-test the condition in a while loop, never an if, because the condition may have been falsified between the signal and the wakeup.

5. Classical Problems

Producer-consumer is the bounded buffer described above, and its lesson is the ordering of waits.

Readers-writers allows any number of concurrent readers but requires a writer to have exclusive access.

The naive solution starves writers. If readers keep arriving while others are reading, the read count never reaches zero and a waiting writer never proceeds. A writer-preference variant fixes this by blocking new readers once a writer is waiting, which then risks starving readers, and a fair variant queues both in arrival order.

Dining philosophers has five philosophers, five forks, and each needing both neighbouring forks to eat.

The obvious solution, picking up the left fork then the right, deadlocks when all five take their left fork simultaneously.

Three standard fixes exist, and each denies a different deadlock condition. Allowing at most four philosophers at the table denies hold-and-wait in effect. Requiring both forks be picked up atomically denies hold-and-wait directly. Making one philosopher pick up the right fork first denies circular wait.

6. Deadlock and Its Four Conditions

Deadlock is a state in which every process in a set is waiting for an event that only another process in that set can cause.

Four conditions must all hold simultaneously.

Mutual exclusion: at least one resource is non-shareable.

Hold and wait: a process holding a resource is waiting to acquire another.

No preemption: a resource cannot be taken from a process; it must be released voluntarily.

Circular wait: a cycle of processes exists, each waiting for a resource held by the next.

Circular wait implies hold and wait, but not conversely, which is why the four are stated as necessary together rather than independent.

A resource allocation graph has processes and resources as nodes, request edges from process to resource, and assignment edges from resource to process.

With one instance per resource type, a cycle is necessary and sufficient for deadlock. With multiple instances, a cycle is necessary but not sufficient, because another holder of the same type may release and break it.

7. Handling Deadlock

Four strategies exist, and systems in practice choose the last one.

Prevention denies one of the four conditions structurally. Requesting all resources at once denies hold and wait but hurts utilisation. Allowing preemption of resources whose state can be saved denies no-preemption. Imposing a total order on resource types and requiring increasing acquisition denies circular wait, and is the technique used in real kernels.

Avoidance uses advance knowledge of maximum needs to stay in safe states. A state is safe if some ordering of the processes exists in which each can obtain its maximum need from currently available resources plus those released by its predecessors.

The banker's algorithm implements avoidance. It grants a request only if the resulting state is safe, computed by repeatedly finding a process whose remaining need is satisfiable and pretending it finishes.

An unsafe state is not a deadlock, only a state from which deadlock is possible. Avoidance is therefore conservative and refuses some requests that would have been fine.

Detection lets deadlock happen and finds it afterwards, running a reducibility algorithm on the allocation matrices periodically.

Recovery then aborts processes or preempts resources, choosing victims by cost, and must handle rollback and the risk of starving the same victim repeatedly.

Ignoring the problem is what most operating systems do. Deadlock is rare enough that a reboot is cheaper than the machinery, an approach honestly named the ostrich algorithm.

8. Worked Examples

Example 1. Show that the two-flag solution without a turn variable violates progress, and that Peterson's fix repairs it.

The flawed version has each thread set its flag to true and then wait while the other's flag is true.

Consider the interleaving where both threads set their flags before either tests. Thread A sets its flag; thread B sets its flag; A tests B's flag and finds it true, so waits; B tests A's flag and finds it true, so waits.

Neither can proceed and neither is in the critical section, so the progress requirement fails. This is deadlock, not merely inefficiency.

Peterson's algorithm adds a turn variable written after the flag. Thread A sets its flag, then sets turn to B. Thread B sets its flag, then sets turn to A.

Whichever write to turn happens second is the one that persists, since the two writes are to the same location.

Suppose A wrote turn last, so turn is B. A's wait condition is that B's flag is set and turn is B, which is true, so A waits. B's condition is that A's flag is set and turn is A, and turn is B, so B enters.

Exactly one thread proceeds and the symmetry is broken by the memory location itself, which is why progress holds.

Bounded waiting also holds: when B leaves it clears its flag, so A enters immediately, and B cannot re-enter ahead of A because re-entering sets turn back to A.

Example 2. In the bounded buffer, the producer executes wait(mutex) before wait(empty). Exhibit a deadlock.

The correct producer waits on empty first, then on mutex. The swapped version does the reverse.

Fill the buffer completely, so the empty semaphore has value zero.

A producer now runs. It executes wait(mutex) successfully, acquiring exclusive access, then executes wait(empty), which blocks because there are no empty slots.

The producer is now blocked while holding the mutex.

A consumer arrives and executes wait(full), which succeeds since the buffer is full, then executes wait(mutex), which blocks because the producer holds it.

Neither can proceed. The producer waits for a slot only the consumer can free; the consumer waits for a mutex only the producer can release.

Map this onto the four conditions. Mutual exclusion holds on the mutex, hold-and-wait holds because the producer holds the mutex while waiting on empty, no preemption holds since semaphores are not seizable, and circular wait holds between the two.

The general rule this illustrates: never block on a resource-counting semaphore while holding a mutex. Acquire the count first and the mutex second, always in that order.

9. More Worked Examples

Example 3. A system has 3 resource types A, B, C with 10, 5, 7 instances. Current allocation and maximum are as follows. Is the state safe?

ProcessAllocation A B CMaximum A B CNeed A B C
P00 1 07 5 37 4 3
P12 0 03 2 21 2 2
P23 0 29 0 26 0 0
P32 1 12 2 20 1 1
P40 0 24 3 34 3 1

Available is total minus allocated, which is for A, for B, and for C, giving (3, 3, 2).

Find any process whose need is at most available.

P0 needs (7, 4, 3), which exceeds available on every component. Skip.

P1 needs (1, 2, 2), which fits. Run it and reclaim its allocation, giving available (5, 3, 2).

P3 needs (0, 1, 1), which fits. Available becomes (7, 4, 3).

P4 needs (4, 3, 1), which fits. Available becomes (7, 4, 5).

P0 needs (7, 4, 3), which now fits. Available becomes (7, 5, 5).

P2 needs (6, 0, 0), which fits. Available becomes (10, 5, 7).

A complete ordering exists, so the state is safe, with safe sequence P1, P3, P4, P0, P2.

Note that the sequence is not unique and any valid one earns the mark.

Example 4. In the state above, P1 requests (1, 0, 2). Should the banker grant it?

Three checks are made in order.

First, is the request within P1's declared need of (1, 2, 2)? Yes, since (1, 0, 2) is componentwise smaller.

Second, is the request within available (3, 3, 2)? Yes.

Third, pretend to grant it and test safety. Available becomes (2, 3, 0), P1's allocation becomes (3, 0, 2) and its need becomes (0, 2, 0).

Now search for a safe sequence. P1 needs (0, 2, 0), which fits in (2, 3, 0), so run it, releasing (3, 0, 2) to give available (5, 3, 2).

P3 needs (0, 1, 1), fits, giving (7, 4, 3). P4 needs (4, 3, 1), fits, giving (7, 4, 5). P0 needs (7, 4, 3), fits, giving (7, 5, 5). P2 needs (6, 0, 0), fits.

The state is safe, so the request is granted.

Had the third check failed, the process would wait even though the resources were physically available, which is exactly the conservatism of avoidance.

Example 5. Three processes each need a maximum of 4 units of a single resource type. What is the smallest total number of units that guarantees the system cannot deadlock?

Deadlock requires every process to be holding units and still needing more.

The worst case is that each process holds one unit fewer than its maximum, since that is the most any can hold while still being blocked.

With 3 processes each holding 3 units, the total held is 9 and every process still needs 1 more.

If the system has exactly 9 units, that state is reachable and is a deadlock.

With 10 units, one unit remains after the worst case, so some process can complete, release all 4, and unblock the rest.

The general formula is that processes each with maximum need cannot deadlock if the total is at least .

Here that is .

The formula generalises to unequal maximums as the sum of all maximums minus the number of processes plus one, which reduces to the same expression when the maximums are equal.

Example 6. Why does a cycle in a resource allocation graph imply deadlock with single-instance resources but not with multiple instances?

With one instance per type, an assignment edge identifies the unique holder. A cycle then means every process in it waits for a resource held by the next, and no process outside the cycle can release any of those resources, because it does not hold them.

The wait is therefore permanent and the cycle is sufficient.

With multiple instances, an assignment edge points from one instance to its holder, and a request edge does not name which instance will satisfy it.

A process in the cycle may be waiting for a type of which some other instance is held by a process outside the cycle.

When that outside process finishes and releases its instance, the waiting process proceeds and the cycle dissolves without anyone in it having done anything.

So a cycle is necessary but not sufficient, and detection with multiple instances requires the reduction algorithm on allocation and request matrices rather than a graph search.

Summary

Every concurrency bug is a claim about an interleaving and every solution restricts which interleavings are possible; to disprove correctness exhibit one bad ordering.

A correct critical section solution needs mutual exclusion, progress and bounded waiting. Mutual exclusion alone is satisfied by a solution that admits nobody, which is why the other two exist.

Peterson's algorithm works by having each thread set turn to the other, so the second write loses the tie. It needs memory barriers on real hardware. Test-and-set and compare-and-swap provide atomicity in hardware, and plain test-and-set locks lack bounded waiting.

A semaphore is an integer plus a blocked queue; when negative, its magnitude counts blocked threads. In the bounded buffer, waiting on the mutex before the counting semaphore deadlocks, which is the ordering rule to remember.

Monitors make mutual exclusion structural. Condition variables have no memory, and under Mesa semantics the woken thread must re-test its condition in a while loop.

Readers-writers starves writers in the naive form; dining philosophers deadlocks under uniform left-then-right acquisition, and each standard fix denies a different deadlock condition.

Deadlock needs mutual exclusion, hold and wait, no preemption and circular wait, all at once. Prevention denies one structurally, with resource ordering the practical choice. The banker's algorithm implements avoidance and refuses requests leading to unsafe states, though unsafe is not the same as deadlocked. Detection plus recovery, or simply ignoring the problem, is what real systems do.

With single-instance resources a cycle means deadlock; with multiple instances it does not. And processes with maximum need cannot deadlock given units.

Key formulas & results

Everything to memorise for the exam hall, in one card. Screenshot this for revision.

The organising principle
a bug is one bad interleaving; a fix is a restriction on interleavings
To disprove correctness, exhibit a single ordering that fails. To argue correctness, show no bad ordering remains.
Three critical-section requirements
mutual exclusion, progress, bounded waiting
Mutual exclusion alone is satisfied by admitting nobody, so progress rules out that degenerate answer and bounded waiting rules out starvation.
Semaphore invariant
when the value is negative, its magnitude equals the number of blocked threads
The fastest route through numerical semaphore questions.
Bounded buffer ordering rule
acquire the counting semaphore before the mutex, never the reverse
Blocking on empty or full while holding the mutex prevents the other party from ever making progress.
Mesa condition variable rule
while (condition not met) wait(cv); never if
The signaller keeps the monitor, so the condition may be falsified between the signal and the wakeup.
Four necessary conditions
mutual exclusion, hold and wait, no preemption, circular wait, all simultaneously
Every prevention technique works by denying exactly one of them structurally.
Safe state
a state is safe if some ordering exists in which each process can obtain its maximum need from available plus what its predecessors release
Unsafe is not deadlocked, only a state from which deadlock is possible, which is why avoidance is conservative.
Banker's request test
request must be within need, within available, and the pretend-granted state must be safe
All three checks, in that order. Failing the third makes the process wait despite resources being physically free.
Minimum resources to avoid deadlock
n processes each with maximum need m cannot deadlock if total units are at least n(m minus 1) plus 1
The worst case has every process holding one short of its maximum; one spare unit lets somebody finish. For unequal maximums, use the sum of maximums minus n plus 1.
Cycle and deadlock
single instance: cycle is necessary and sufficient; multiple instances: necessary but not sufficient
With multiple instances a holder outside the cycle can release and dissolve it.
⚠️

Traps GATE sets — and how to dodge them

These are the exact option-traps and misreads that cost marks under negative marking.

WATCH OUT
Accepting a solution because it achieves mutual exclusion
Test progress by asking whether both threads can be simultaneously blocked while the critical section is empty, and bounded waiting by asking whether one thread can be overtaken indefinitely.
Why it happens: Mutual exclusion is the requirement students remember, and the other two feel like refinements rather than requirements.
WATCH OUT
Writing Peterson's algorithm with turn set to one's own thread
Each thread yields the turn to the other. The write that lands second decides, and setting your own turn makes both threads think it is theirs.
Why it happens: It reads more naturally as claiming a turn than as yielding one.
WATCH OUT
Swapping the two waits in the producer of a bounded buffer
Wait on empty or full first, then on the mutex. Blocking on a count while holding the mutex is the deadlock.
Why it happens: Both waits are present, so the code looks complete, and the mutex-first order matches the habit of locking before touching shared state.
WATCH OUT
Using if instead of while around a condition variable wait
Real systems use Mesa semantics, so always re-test in a while loop. Assume Mesa unless the question says otherwise.
Why it happens: Under Hoare semantics an if would be correct, and textbook pseudocode is not always explicit about which discipline it assumes.
WATCH OUT
Treating an unsafe state as a deadlock
Unsafe means deadlock is possible under worst-case behaviour, not that it has occurred. Many unsafe states never deadlock.
Why it happens: The banker's algorithm refuses unsafe states, which makes them sound like failures.
WATCH OUT
Concluding deadlock from a cycle with multiple resource instances
With multiple instances, check whether some holder outside the cycle can release an instance of the contested type. If so, there is no deadlock.
Why it happens: The single-instance rule is taught first and is stated as an equivalence, so the qualification is easily dropped.
WATCH OUT
Forgetting that available equals total minus the sum of allocations
Compute available componentwise as total minus the column sums of the allocation matrix before doing anything else.
Why it happens: Banker's questions often give total resources and the allocation matrix but not the available vector, and students look for it in the table.
WATCH OUT
Using n times m for the minimum-resource formula
The worst reachable blocked state has each process one unit short, so n(m minus 1) deadlocks and one more unit breaks it.
Why it happens: It seems safe to provide everyone their full maximum, and the correct expression looks like an off-by-one variant of it.

Exam-pattern practice

PYQ-style questions with full solutions. Work through them as a readiness check — mark yourself honestly and get your gap report at the end.

Readiness check

Are you exam-ready for Concurrency, Synchronization & Deadlock?

10 problems from this chapter. Try each one, reveal the worked solution, mark yourself honestly — get your gap report at the end.

10 questions~7 min

5-minute revision

The whole chapter, distilled. Read this the night before the exam.

  • A bug is one bad interleaving; a fix restricts interleavings
  • Three requirements: mutual exclusion, progress, bounded waiting
  • Peterson sets turn to the other thread, so the second write loses the tie
  • Peterson needs memory barriers on real hardware, so it is a teaching device
  • Test-and-set and compare-and-swap are atomic; plain test-and-set locks lack bounded waiting
  • Semaphore value negative means that many threads are blocked
  • Bounded buffer: wait on empty or full before the mutex, never after
  • Monitors make mutual exclusion structural; condition variables have no memory
  • Mesa semantics forces a while loop around every condition wait
  • Naive readers-writers starves writers; writer preference can starve readers
  • Dining philosophers deadlocks under uniform left-then-right; each fix denies one condition
  • Four conditions: mutual exclusion, hold and wait, no preemption, circular wait
  • Prevention denies one structurally; resource ordering is the practical technique
  • Banker's algorithm avoids unsafe states; unsafe is not the same as deadlocked
  • Real systems mostly ignore deadlock, the ostrich algorithm
  • Single instance: cycle equals deadlock. Multiple: cycle is necessary only
  • n processes with maximum m are deadlock-free given n(m minus 1) plus 1 units

GATE question blueprint

How this topic is asked, tier by tier — so you can prep to the pattern.

Typical weightage: 7

Question styleMarks eachTypical countWhat it tests
Semaphores and monitors21
Banker's algorithm21
Deadlock conditions11
Critical section11
Classical problems11

Exam-hall strategy

Battle-tested tips from mentors and toppers for this topic under the sectional clock.

  1. For critical-section questions, test the three requirements in order and stop at the first violation, since the answer is usually progress. For semaphore code, look first at the order of waits and second at any missing signal on an error path. Banker's questions are pure bookkeeping: compute available from total minus column sums, write the need matrix explicitly, then sweep the process list repeatedly. Any valid safe sequence earns the mark, so take the first process that fits rather than searching for a canonical answer. For minimum-resource questions apply the formula directly rather than reasoning case by case. When a resource allocation graph appears, check the instance count before concluding anything from a cycle.

Beyond the exam

Where this skill shows up in the job you're competing for — and in life.

The Linux kernel documents a strict lock ordering and shi…

The Linux kernel documents a strict lock ordering and ships lockdep, a runtime validator that reports any acquisition violating the established order before a real deadlock occurs

Java's synchronized blocks and wait-notify are monitors w…

Java's synchronized blocks and wait-notify are monitors with Mesa semantics, which is why the language documentation insists on the while loop

Lock-free queues in high-frequency trading and in the Jav…

Lock-free queues in high-frequency trading and in the Java concurrency library are built on compare-and-swap precisely because it avoids blocking entirely

Database engines use two-phase locking with deadlock dete…

Database engines use two-phase locking with deadlock detection and victim rollback, since transactions can be safely aborted and retried in a way that OS processes generally cannot

The dining philosophers pattern reappears whenever a syst…

The dining philosophers pattern reappears whenever a system acquires several locks per operation, as in a bank transfer that must lock two accounts

Where else this topic is tested

Prepare once, score in every exam that asks it.

GATE CS
GATE DA
UGC NET Computer Science
ISRO Scientist SC
BARC Computer Science

Questions aspirants ask

Pulled from the Q&A community and mentor sessions.

Not as written. It assumes sequentially consistent memory, and modern processors reorder writes, so a thread can see the other's turn write before its flag write and both may enter. Adding explicit memory barriers makes it correct but slower than a hardware atomic instruction, so it survives as a teaching example rather than an implementation.

When the expected wait is shorter than the cost of two context switches. Kernel spinlocks protect very short critical sections on multiprocessors for exactly this reason, and they must never be held while sleeping, because the waiter is burning a CPU that the holder might need.

Mutual exclusion says only one thread is inside; it says nothing about a thread that is inside but cannot make progress. Without a way to release the monitor and wait, such a thread would hold the monitor and block everyone. The condition variable provides the atomic release-and-block step.

It requires each process to declare its maximum resource need in advance, which is unknowable for general programs, and the safety check costs time proportional to the number of processes times the number of resource types on every request. It also assumes a fixed resource population, which fails when devices come and go.

If every process acquires resources in increasing order of a global numbering, a cycle would require some process to hold a higher-numbered resource while requesting a lower-numbered one, which the rule forbids. Real kernels document such a lock ordering and check it with debugging tools.

Yes, if the process acquires a non-reentrant lock and then attempts to acquire it again, which is self-deadlock. The four conditions are still satisfied, with a circular wait of length one, and this is why reentrant mutexes exist.
Header Logo