Operating Systems 📂 Deadlock · 2 of 3 60 min read

Deadlock Prevention & Avoidance

Master Deadlock Prevention and Avoidance from Galvin's Operating System Concepts with four interactive animations and two worked numericals. Watch ordered locking prevent circular wait, walk through Banker's Safety Algorithm on Galvin's classic 5-process example, trace the resource-request algorithm across three scenarios (granted, insufficient, unsafe-denied), and solve a second numerical step-by-step. Includes both prevention protocols for all four Coffman conditions.

Section 01

The Story That Explains Prevention & Avoidance

The Cautious Bank Manager
A bank has ₹10 lakh in reserve. Four regular customers have credit lines that could eventually reach ₹7L, ₹5L, ₹3L, and ₹9L — a total maximum demand of ₹24L, far more than the bank actually holds. This is fine as long as they don't all request their maximum at the same time.

A customer walks in and asks for ₹3L on top of the ₹2L they already borrowed. The manager doesn't just look at "do I have ₹3L in the vault today?" — she asks the deeper question: "If I hand over this money right now, is there still a scenario in which every customer can eventually be fully repaid without any of them getting stuck waiting?"

If yes → grant. If no → politely ask them to wait, even though the money is physically available. This conservative rule is exactly Dijkstra's Banker's Algorithm. In an OS, "money" is CPU/memory/mutex/file-handles, and "customers" are processes.
💡
Prevention vs Avoidance — The Key Difference

Prevention makes deadlock structurally impossible — it removes one of the four Coffman conditions at design time, so no runtime check is ever needed. Avoidance lets all four conditions exist, but the OS makes each allocation decision carefully at runtime, refusing any request that could later lead to deadlock.


Section 02

Quick Recap — The Four Coffman Conditions

Before we can break any condition, remember all four must simultaneously hold for a deadlock to occur. Prevention strategies attack these one by one.

#ConditionWhat It Means
1Mutual ExclusionAt least one resource is non-sharable — only one process at a time.
2Hold and WaitA process holds some resources while requesting more.
3No PreemptionResources cannot be forcibly taken back — released voluntarily.
4Circular WaitA closed chain of processes exists, each waiting for the next.
🔑
Prevention Strategy in One Line

Pick any one of the four conditions and design your system so it cannot occur. Deadlock becomes mathematically impossible. Sections 3–6 examine each strategy in turn.


Section 03

Prevention Strategy 1 — Break Mutual Exclusion

If a resource can be shared by multiple processes, no process ever needs to wait, and therefore no deadlock. Sadly, most interesting resources are intrinsically non-sharable: a printer cannot print two documents mid-page, a mutex cannot be locked twice.

✅ Where It Works
Read-only files — infinitely many readers, no writers → sharable
Spooled printers — jobs pile in a spool file, printer serves them serially
Immutable data structures — every "modification" produces a new copy
❌ Where It Fails
Mutex locks — the entire point is exclusive access
Write locks on files — cannot allow two concurrent writers
Physical single-user devices — CD burner, tape drive
⚠️
Galvin's Verdict

In general, we cannot prevent deadlocks by denying mutual exclusion because some resources are fundamentally exclusive. This strategy has limited applicability.


Section 04

Prevention Strategy 2 — Break Hold and Wait

A process may not hold any resource while requesting others. Two protocols implement this.

💼
Protocol A — Request Everything Upfront
All-or-nothing
Before executing, a process must request all resources it will ever need. If any are unavailable, wait until all can be granted together.
💫
Protocol B — Release Before Requesting
Full reset
A process may request additional resources only after releasing everything it currently holds. Then it must re-acquire all at once.
🔌
Both Protocols Share
Same trade-offs
Low resource utilisation (resources held but unused for long periods) and possible starvation (a process needing many popular resources may wait indefinitely for all to be free simultaneously).

Example Comparison

A copy program copies data from tape (T) to disk (D), then prints results (P). Under Protocol A, it grabs T + D + P at the start — even though P isn't used for hours of copying. Under Protocol B, it grabs T + D, does the copy, releases both, then grabs D + P and prints. The middle release point creates a race: another process might snatch D and the copy program blocks.


Section 05

Prevention Strategy 3 — Break No Preemption

Allow the OS to forcibly take resources back from a waiting process. Two rules apply.

🔒 Preemption Protocol
Rule 1
If a process holding some resources requests another that cannot be immediately granted, all its current resources are preempted (released implicitly).
Rule 2
The preempted process must reacquire all the released resources plus the new one before it can resume. It cannot make partial progress.
Rule 3 (alt)
Alternatively, if process Pi requests a resource held by waiting process Pj, we may preempt from Pj and give to Pi.
⚠️
Where This Works — and Doesn't

Preemption works for resources whose state can be saved and restored: CPU registers, memory pages. It does not work for printers (mid-page abort ruins the output) or write locks on files (partial writes may corrupt the file). Common in memory management, rare elsewhere.


Section 06

Prevention Strategy 4 — Break Circular Wait (Most Practical)

Impose a total ordering on all resource types. Assign each type a unique integer F(Ri). Every process must request resources in strictly increasing order of F. To request a resource lower in the order, first release all resources of equal or higher order.

🔑
Why It Works — Proof Sketch

Suppose a circular wait exists: P0 → P1 → … → Pn → P0. This would mean Pi holds a resource with a higher F than the one it's waiting for. But every process only requests higher F values, never lower. Contradiction → no cycle can exist → no deadlock.

🎮 Interactive — Watch Ordered Locking Prevent Deadlock

Two processes both need R1 (order=1) and R2 (order=2). With the ordering rule enforced, both must acquire R1 first, then R2. Click Next to trace the execution.

Ordered Locking — no cycle, no deadlock
P1 wants R1+R2 P2 wants R1+R2 R1 order = 1 free R2 order = 2 free ⏸ WAITING for R1 ready RULE: always acquire in order R1 → R2 ✅ NO DEADLOCK POSSIBLE
Step 0 of 7
START Two processes want both R1 and R2. Rule: every process must acquire lower-order resources first. R1 (order=1) before R2 (order=2). Click Next to see how the rule stops any cycle from forming.
🏆
Why This Is the Most Common Fix in Production

Ordered locking is easy to implement, has zero runtime overhead, and requires no OS cooperation — just a documented convention. Linux kernel code, PostgreSQL, and virtually every large C++ codebase use this pattern. Static analysis tools like Linux's lockdep automatically detect violations.


Section 07

Deadlock Avoidance — Concept

Prevention is pessimistic — it disallows patterns that could deadlock even when they wouldn't. Avoidance is optimistic — it grants requests whenever safe and postpones only truly risky ones. The trade-off is that avoidance requires a priori information: each process must declare its maximum future resource need.

The Three Regions of System State
State-Space Diagram UNSAFE SAFE DEADLOCK granting an unsafe request Avoidance keeps the system inside SAFE forever. Deadlock is a subset of UNSAFE, not all of it.

Safe State — Formal Definition

A state is safe if there exists a safe sequence ⟨P1, P2, …, Pn⟩ of all processes such that, for each Pi, the resources Pi may still request can be satisfied by:

💵
Safe Sequence Condition

Needi ≤ Available + Σj < i Allocationj

In plain English: Pi's remaining need can be met by currently free resources plus everything held by processes that finish before it.


Section 08

Banker's Algorithm — Overview

Dijkstra's Banker's Algorithm is the classic implementation of avoidance for systems with multiple instances of each resource type. Named for a bank that only lends when it can still guarantee all customers can be fully repaid.

💼 Data Structures — n processes, m resource types
Available[m]
Vector of length m. Available[j] = number of instances of resource j currently free.
Max[n][m]
Matrix. Max[i][j] = maximum instances of resource j that process i may ever request. Declared up front by the process.
Allocation[n][m]
Matrix. Allocation[i][j] = instances of resource j currently held by process i.
Need[n][m]
Matrix. Need[i][j] = Max[i][j] − Allocation[i][j]. How much more process i might still need.
🚩
Key Assumption

The Banker's Algorithm only works if every process declares its maximum need in advance — and never exceeds it. This is often unrealistic in general-purpose operating systems, which is why avoidance is used mostly in real-time and embedded systems where workloads are known.


Section 09

The Safety Algorithm

Given the current state (Available, Allocation, Need), the safety algorithm asks: "Does a safe sequence exist?" It simulates completion of processes in a hypothetical order.

# Determine whether the system is in a safe state
Work    = Available                     # snapshot of free resources
Finish  = [false] * n                 # has each process finished?

while exists i such that Finish[i] == false and Need[i] <= Work:
    Work      = Work + Allocation[i]    # pretend Pi finishes, returns its resources
    Finish[i] = true

if all(Finish):
    return "SAFE — a safe sequence exists"
else:
    return "UNSAFE — deadlock is possible"
📈
Complexity

Time complexity: O(m·n²) — for each of n selections we may scan up to n processes across m resource types. Runs every time a request arrives, so avoid for high-frequency allocation.

🎮 Interactive — Walk Through Banker's Safety Check (Galvin's Classic)

Setup: 5 processes (P0–P4), 3 resource types (A, B, C). Total instances: A=10, B=5, C=7. Currently allocated: A=7, B=2, C=5, so Available = [3, 3, 2]. Click Next to test one process at a time.

Banker's Safety Algorithm — Galvin's textbook example
Allocation
ABC
P0010
P1200
P2302
P3211
P4002
Max
ABC
P0753
P1322
P2902
P3222
P4433
Need = Max − Alloc
ABC
P0743
P1122
P2600
P3011
P4431
Work vector (currently free)
Work = [ 3, 3, 2 ]
Initially: Work = Available = [3, 3, 2]
Safe sequence being built
⟨ ⟩
Ready to begin safety check…
Step 0 of 6
START Compute Need = Max − Allocation for each row (already shown). Initialise Work = Available = [3, 3, 2]. All processes have Finish = false. Click Next to test one process at a time.
🏆
Result

The safe sequence ⟨P1, P3, P4, P0, P2⟩ exists. The system is in a SAFE state. Multiple safe sequences may exist — any one is sufficient to prove safety.


Section 10

Resource-Request Algorithm

When process Pi makes a request Requesti, the Banker doesn't grant it blindly. It pretends to grant, runs the safety algorithm, and only truly commits if the result is safe. Otherwise, roll back and make Pi wait.

def resource_request(i, Request):
    # Step 1 — sanity check: can't ask for more than declared max
    if Request > Need[i]:
        raise Error("Process exceeded its Max declaration")

    # Step 2 — physical check: is there enough right now?
    if Request > Available:
        return "WAIT — not enough resources yet"

    # Step 3 — pretend to grant
    Available     -= Request
    Allocation[i] += Request
    Need[i]       -= Request

    # Step 4 — safety check on the pretended state
    if is_safe():
        return "GRANTED"                # commit the change
    else:
        # Roll back — request would lead to unsafe state
        Available     += Request
        Allocation[i] -= Request
        Need[i]       += Request
        return "DENIED — unsafe, must wait"

🎮 Interactive — Two Requests Handled by the Banker

Same initial state as before. Watch the Banker evaluate two requests: one is granted, the next is refused because it would drive the system into an unsafe state.

Resource-Request Algorithm — safe request granted, unsafe request denied
Current request
— no request yet —
Available (Work)
[ 3, 3, 2 ]
Allocation & Need after (pretend) grant
ProcessAllocationNeed
ABCABC
P0010743
P1200122
P2302600
P3211011
P4002431
Verdict
Awaiting requests…
Step 0 of 8
START We begin with the same state as Section 09: Available = [3, 3, 2]. Two request scenarios will follow. Click Next to submit the first request from P1.

Section 11

Numerical Problem 1 — Fully Worked Static Example

Given the following system state, determine whether it is safe. If safe, find a safe sequence.

Problem Statement

📋 Given
Number of processes: 4 (P0, P1, P2, P3)
Number of resource types: 3 (A, B, C)
Total instances: A = 8, B = 5, C = 6
📊 Allocation Matrix
ABC
P0102
P1311
P2210
P3101
📊 Max Matrix
ABC
P0433
P1423
P2522
P3533
📊 Need = Max − Allocation
ABC
P0331
P1112
P2312
P3432

Step 1 — Compute Available

# Total allocated per column
Sum_A = 1 + 3 + 2 + 1 = 7
Sum_B = 0 + 1 + 1 + 0 = 2
Sum_C = 2 + 1 + 0 + 1 = 4

# Available = Total − Sum
Available_A = 87 = 1
Available_B = 52 = 3
Available_C = 64 = 2

Available = [1, 3, 2]

Step 2 — Run Safety Algorithm

📊 Safety Trace — Work updates after each process finishes
Init
Work = [1, 3, 2], Finish = [F, F, F, F].
Try P0
Need = [3, 3, 1]. Compare with Work [1, 3, 2]: 3 > 1 → fail.
Try P1
Need = [1, 1, 2]. Compare with Work [1, 3, 2]: 1 ≤ 1 ✓, 1 ≤ 3 ✓, 2 ≤ 2 ✓ → P1 selected. Work = [1, 3, 2] + [3, 1, 1] = [4, 4, 3]. Finish[1] = true.
Try P0
Need = [3, 3, 1] vs Work [4, 4, 3]: 3 ≤ 4 ✓, 3 ≤ 4 ✓, 1 ≤ 3 ✓ → P0 selected. Work = [4, 4, 3] + [1, 0, 2] = [5, 4, 5]. Finish[0] = true.
Try P2
Need = [3, 1, 2] vs Work [5, 4, 5]: 3 ≤ 5 ✓, 1 ≤ 4 ✓, 2 ≤ 5 ✓ → P2 selected. Work = [5, 4, 5] + [2, 1, 0] = [7, 5, 5]. Finish[2] = true.
Try P3
Need = [4, 3, 2] vs Work [7, 5, 5]: 4 ≤ 7 ✓, 3 ≤ 5 ✓, 2 ≤ 5 ✓ → P3 selected. Work = [7, 5, 5] + [1, 0, 1] = [8, 5, 6]. Finish[3] = true.
Done
All Finish = true. Final Work [8, 5, 6] matches total resources. System is SAFE.
🏆
Answer

Yes, the system is safe. A valid safe sequence is ⟨P1, P0, P2, P3⟩.


Section 12

Numerical Problem 2 — Interactive Walkthrough

Consider a different system. Click Next to run the safety algorithm one step at a time.

Setup

📋 Given
Processes: 4 (P0, P1, P2, P3)
Resource types: 3 (X, Y, Z)
Total: X = 9, Y = 3, Z = 6
Available (already computed): [1, 1, 2]
📊 Matrices
AllocMaxNeed
XYZXYZXYZ
P0211513302
P1101212111
P2412723311
P3100211111
Numerical 2 — Safety Algorithm on a 4×3 system
Allocation
XYZ
P0211
P1101
P2412
P3100
Need
XYZ
P0302
P1111
P2311
P3111
Work vector
[ 1, 1, 2 ]
Initially: Work = [1, 1, 2]
Safe sequence
⟨ ⟩
Click Next to test each process for satisfiability…
Step 0 of 5
START Available = [1, 1, 2]. All Finish = false. We'll iterate through P0, P1, P2, P3 and pick the first one whose Need ≤ Work.

Section 13

Advantages & Disadvantages

✅ Advantages of Banker's Algorithm
Prevents deadlock without pessimistic restrictions on programming style
Allows the four Coffman conditions to all exist — natural code, natural mutexes
Provides a mathematical guarantee: if all requests stay within Max, deadlock is impossible
Works with multiple instances per resource type
❌ Disadvantages
Max declaration required — often unrealistic in general-purpose OS
Process count and resource count must be fixed — no dynamic joins
O(m·n²) overhead per request — expensive at high frequency
Processes must eventually return resources within finite time (must not hang mid-work)
Conservative — rejects some requests that would in practice be safe

Section 14

Comparison — Prevention vs Avoidance

AspectPreventionAvoidance (Banker's)
When to actDesign time / compile timeRuntime, per request
AttitudePessimisticOptimistic — grants when safe
Runtime overheadNoneO(m·n²) per request
Requires max declaration?NoYes — up front
Device utilisationOften lowUsually high
Best forGeneral-purpose OS, mutex-heavy codeReal-time, batch, embedded systems
ExampleOrdered lock acquisition in Linux kernelReal-time task schedulers with known workload

Section 15

Real-World Applications

💾
Linux Kernel Lock Ordering
Linux enforces documented lock hierarchies. The lockdep runtime checker dynamically tracks acquisition order and warns on any violation that could theoretically cause deadlock.
Prevention · Circular Wait
📡
RT-OS Task Scheduling
VxWorks and RTEMS use Banker-style avoidance when max resource needs are declared per task at build time — common in aerospace and industrial control.
Avoidance · Banker's
🖥️
Java Concurrency
Java's ReentrantLock.tryLock(timeout) lets developers back out of a lock attempt — effectively breaking the "no preemption" condition through voluntary release.
Prevention · No Preemption
🛡️
Aviation Software (DO-178C)
Certified flight control software mandates provably deadlock-free designs. Ordered locking and static analysis prevent any circular wait from being possible.
Prevention · Formal Verification
💰
Distributed Databases
Systems like Spanner and CockroachDB use timestamp-based approaches (wound-wait, wait-die) — distributed analogues of preemption-based deadlock handling.
Prevention · Preemption
🎮
Game Engine Job Systems
Unity Jobs and Unreal Task Graph express task dependencies as a DAG. A topological sort before scheduling proves no circular wait can occur — pure prevention.
Prevention · Circular Wait

Section 16

Golden Rules — Prevention & Avoidance

🔑 Galvin's Non-Negotiable Rules
1
To prevent deadlock, break at least one of the four Coffman conditions. Only one — but pick the practical one for your system.
2
Mutual exclusion cannot be avoided in general — some resources are intrinsically non-sharable. Don't waste time trying to break this condition unless read-only or spoolable.
3
The most practical prevention technique is imposing a total order on resource types and requiring acquisitions in increasing order. Prevents 90% of production deadlocks with zero runtime cost.
4
A system is safe if a safe sequence exists; unsafe otherwise. Deadlock is a subset of unsafe — unsafe does not automatically mean deadlocked.
5
The Banker's Algorithm requires two matrices (Max and Allocation) plus two vectors (Available and Need = Max − Allocation). Master this vocabulary.
6
The safety algorithm is a hypothetical simulation: pretend to grant every process its full Need in some order, adding released resources to Work as each process "finishes". If all finish, state is safe.
7
The resource-request algorithm pretends to grant, runs safety, and rolls back if unsafe. This is the actual runtime decision procedure.
8
Multiple safe sequences may exist for a given state. Finding any one is sufficient to declare the state safe.
9
Banker's overhead is O(m·n²) per request. Do not use it for high-frequency allocation like malloc — save it for coarse-grained resources.
10
Prevention is pessimistic; avoidance is optimistic. Pick prevention when you cannot know max needs in advance; pick avoidance when you can and utilisation matters.