Skip to content

Deadlocks | Computer Science - Wyatt's Notes

flowchart TD
A[4_Deadlocks] --> B[Key Concepts]
A --> C[Core Principles]
A --> D[Practical Applications]
B --> E[Fundamental definitions]
C --> F[Design patterns]
D --> G[Real-world usage]

Deadlock is a circular wait — two or more processes are each holding a resource the other needs, and neither will release what they have. Think of a traffic jam at a four-way intersection where each car refuses to back up. The critical insight is that deadlock is not about a single resource being busy; it’s about a cycle of dependencies.

Why the four conditions matter: All four Coffman conditions (mutual exclusion, hold-and-wait, no preemption, circular wait) must hold simultaneously for deadlock to occur. Breaking any one condition prevents deadlock. Resource ordering (preventing circular wait) and allowing preemption are the most practical strategies.

Recovery intuition: When deadlock occurs, you must either kill processes (lose work) or preempt resources (roll back to a checkpoint). Both are expensive. Prevention (breaking conditions before deadlock can form) is almost always better than recovery.

A deadlock is a situation where a set of processes are all blocked, each waiting for a resource Held by another process in the set.

Coffman conditions (all four must hold simultaneously):

  1. Mutual exclusion: At least one resource is non-sharable.
  2. Hold and wait: A process holds at least one resource and is waiting for others.
  3. No preemption: Resources cannot be forcibly taken.
  4. Circular wait: A circular chain of processes exists, each waiting for a resource held by the next.

Eliminate one of the four Coffman conditions:

ConditionPrevention strategy
Mutual exclusionGenerally not feasible for non-sharable resources
Hold and waitRequire all resources upfront before execution
No preemptionRelease held resources if a new request cannot be granted
Circular waitImpose a total ordering on resources; request in increasing order

4.3 Deadlock Avoidance: Banker”s Algorithm

Section titled “4.3 Deadlock Avoidance: Banker”s Algorithm”

The Banker’s algorithm avoids deadlock by checking whether granting a request leads to a safe State.

Definitions:

  • Available: Vector A=(A1,,Am)A = (A_1, \ldots, A_m) of available instances of each resource type.
  • Maximum: Matrix Max\mathrm{Max} where Max[i][j]\mathrm{Max}[i][j] is the maximum demand of process ii for resource jj.
  • Allocation: Matrix Alloc\mathrm{Alloc} where Alloc[i][j]\mathrm{Alloc}[i][j] is the current allocation.
  • Need: Need[i][j]=Max[i][j]Alloc[i][j]\mathrm{Need}[i][j] = \mathrm{Max}[i][j] - \mathrm{Alloc}[i][j].

Safety algorithm:

1. Work = Available; Finish[i] = false for all i
2. Find i such that Finish[i] == false AND Need[i] <= Work
3. If no such i exists, go to step 5
4. Work = Work + Alloc[i]; Finish[i] = true; go to step 2
5. If Finish[i] == true for all i, system is SAFE

Resource request algorithm. When process ii requests resources:

1. If Request[i] > Need[i], error (exceeded maximum claim)
2. If Request[i] > Available, process must wait
3. Pretend to allocate:
Available = Available - Request[i]
Alloc[i] = Alloc[i] + Request[i]
Need[i] = Need[i] - Request[i]
4. Run safety algorithm.
If safe, grant the request.
If unsafe, roll back and make the process wait.

Example. Five processes, three resource types, A=(3,3,2)A = (3, 3, 2):

ProcessAllocationMaxNeed
P0P_0(0,1,0)(7,5,3)(7,4,3)
P1P_1(2,0,0)(3,2,2)(1,2,2)
P2P_2(3,0,2)(9,0,2)(6,0,0)
P3P_3(2,1,1)(2,2,2)(0,1,1)
P4P_4(0,0,2)(4,3,3)(4,3,1)

Safety check: P1P_1 has Need=(1,2,2)(3,3,2)=A\mathrm{Need} = (1,2,2) \leq (3,3,2) = A. Execute P1P_1Release (2,0,0)(2,0,0)New A=(5,3,2)A = (5,3,2). Then P3P_3: Need=(0,1,1)(5,3,2)\mathrm{Need} = (0,1,1) \leq (5,3,2). Continuing, All processes can complete: system is safe.

Worked Example 4.1 — Banker's Algorithm Step-by-Step

Given the state above, suppose P1P_1 requests (1,0,2)(1,0,2).

Step 1: Verify Request1=(1,0,2)Need1=(1,2,2)\mathrm{Request_1} = (1,0,2) \leq \mathrm{Need_1} = (1,2,2). OK.

Step 2: Verify Request1=(1,0,2)A=(3,3,2)\mathrm{Request_1} = (1,0,2) \leq A = (3,3,2). OK.

Step 3: Pretend to allocate:

ProcessAllocationMaxNeed
P0P_0(0,1,0)(7,5,3)(7,4,3)
P1P_1(3,0,2)(3,2,2)(0,2,0)
P2P_2(3,0,2)(9,0,2)(6,0,0)
P3P_3(2,1,1)(2,2,2)(0,1,1)
P4P_4(0,0,2)(4,3,3)(4,3,1)

A=(3,3,2)(1,0,2)=(2,3,0)A = (3,3,2) - (1,0,2) = (2,3,0).

Step 4: Run safety algorithm.

  1. P1P_1: Need=(0,2,0)(2,3,0)\mathrm{Need} = (0,2,0) \leq (2,3,0). Execute, A=(2,3,0)+(3,0,2)=(5,3,2)A = (2,3,0) + (3,0,2) = (5,3,2).
  2. P3P_3: Need=(0,1,1)(5,3,2)\mathrm{Need} = (0,1,1) \leq (5,3,2). Execute, A=(5,3,2)+(2,1,1)=(7,4,3)A = (5,3,2) + (2,1,1) = (7,4,3).
  3. P4P_4: Need=(4,3,1)(7,4,3)\mathrm{Need} = (4,3,1) \leq (7,4,3). Execute, A=(7,4,3)+(0,0,2)=(7,4,5)A = (7,4,3) + (0,0,2) = (7,4,5).
  4. P0P_0: Need=(7,4,3)(7,4,5)\mathrm{Need} = (7,4,3) \leq (7,4,5). Execute, A=(7,4,5)+(0,1,0)=(7,5,5)A = (7,4,5) + (0,1,0) = (7,5,5).
  5. P2P_2: Need=(6,0,0)(7,5,5)\mathrm{Need} = (6,0,0) \leq (7,5,5). Execute, A=(7,5,5)+(3,0,2)=(10,5,7)A = (7,5,5) + (3,0,2) = (10,5,7).

All processes finish. The request is granted. Safe sequence: P1,P3,P4,P0,P2\langle P_1, P_3, P_4, P_0, P_2 \rangle.

Worked Example 4.2 — Unsafe State Detection

Suppose instead P0P_0 requests (0,2,0)(0,2,0) in the original state.

Step 1: Request0=(0,2,0)Need0=(7,4,3)\mathrm{Request_0} = (0,2,0) \leq \mathrm{Need_0} = (7,4,3). OK.

Step 2: Request0=(0,2,0)A=(3,3,2)\mathrm{Request_0} = (0,2,0) \leq A = (3,3,2). OK.

Step 3: Pretend to allocate. New A=(3,1,2)A = (3,1,2), Need0=(7,2,3)\mathrm{Need_0} = (7,2,3).

Step 4: Safety check. No process can execute: P1P_1 needs (1,2,2)(1,2,2) but only (3,1,2)(3,1,2) available (second component insufficient). P3P_3 needs (0,1,1)(3,1,2)(0,1,1) \leq (3,1,2) — OK, execute P3P_3: A=(5,2,3)A = (5,2,3). Then P1P_1: (1,2,2)(5,2,3)(1,2,2) \leq (5,2,3) — OK, execute: A=(7,2,3)A = (7,2,3). But now P0P_0 needs (7,2,3)(7,2,3) — exact match, execute: A=(7,3,3)A = (7,3,3). P4P_4: (4,3,1)(7,3,3)(4,3,1) \leq (7,3,3) — OK, execute: A=(7,3,5)A = (7,3,5). P2P_2: (6,0,0)(7,3,5)(6,0,0) \leq (7,3,5) — OK.

Safe sequence: P3,P1,P0,P4,P2\langle P_3, P_1, P_0, P_4, P_2 \rangle. The request is granted.

Theorem 4.1. The Banker’s algorithm is correct: if it declares a state safe, there exists a Sequence of process completions that avoids deadlock.

Proof. The safety algorithm constructs an explicit sequence. Each process in the sequence can run To completion with the currently available resources plus those released by previously completed Processes. If no such sequence exists, there is a set of processes whose combined needs exceed Available resources. \blacksquare

Allow deadlocks to occur, then detect and recover.

Detection algorithm:

1. Work = Available
Finish[i] = false if Alloc[i] != 0; true otherwise
2. Find i such that Finish[i] == false AND Request[i] <= Work
3. If found: Work = Work + Alloc[i]; Finish[i] = true; go to step 2
4. If not found: processes with Finish[i] == false are deadlocked

Recovery strategies:

  1. Process termination: Kill deadlocked processes one at a time until the cycle is broken.
  2. Resource preemption: Forcibly reclaim resources; must handle rollback of the affected process.
  3. Checkpoint and rollback: Periodically save process state; restore on deadlock.
Worked Example 4.3 — Deadlock Detection

Three processes and one resource type with 10 instances:

ProcessAllocationMaximum
P0P_059
P1P_124
P2P_227

Available = 10(5+2+2)=110 - (5 + 2 + 2) = 1.

Request0=4\mathrm{Request_0} = 4, Request1=2\mathrm{Request_1} = 2, Request2=5\mathrm{Request_2} = 5.

Detection:

  1. Work = 1. No process has Request1\mathrm{Request} \leq 1.
  2. No process can proceed. All three are deadlocked.

The system is in an unsafe state with a deadlock involving {P0,P1,P2}\{P_0, P_1, P_2\}.

Recovery: Preempt 3 units from P0P_0 (reducing its allocation to 2). Now Available = 4. P1P_1 can proceed (Request1=24\mathrm{Request_1} = 2 \leq 4). After P1P_1 finishes, Available = 4+2=64 + 2 = 6. P0P_0: Request0=46\mathrm{Request_0} = 4 \leq 6Proceeds. After: Available = 6+5=116 + 5 = 11. P2P_2 proceeds. Deadlock resolved.

Most modern OSes (Linux, Windows) use the ostrich algorithm: ignore deadlocks and rely on process Termination or reboot. Rationale: deadlocks are rare, detection is expensive, and prevention is Overly restrictive.

Confusing deadlock with livelock: Deadlock is when processes wait indefinitely for each other. Livelock is when processes change state but make no progress (e.g., both backing up and retrying). They’re different concurrency problems.

Assuming all four Coffman conditions must be present for deadlock: All four must hold simultaneously. If even one is broken, deadlock cannot occur. Focus on breaking one condition.

Forgetting that deadlock prevention can reduce concurrency: Strategies like resource ordering or requiring all resources upfront limit how processes can request resources. There’s always a trade-off.