The Story That Explains Deadlock
Both engines run. Both drivers glare. Nobody moves. Ever.
This is a deadlock: a state where every member of a group is waiting for another member to release something they hold — while they themselves hold something the others need. No external help, no motion possible.
In an operating system, the "cars" are processes, and the "bridge" is a shared resource (printer, disk, database record, memory). When a set of processes locks up this way, the OS must either prevent it, avoid it, or detect and recover from it. That's what this chapter is about.
A set of processes is in a deadlocked state when every process in the set is waiting for an event that can be caused only by another process in the set. The event is usually acquisition of a resource. Since every process is waiting, none can trigger the event, and the wait is infinite.
System Model — Resources and Their Life Cycle
A system consists of a finite set of resource types R1, R2, …, Rm. Each type has some number of identical instances: for example, "the CPU" has 8 instances (cores), "the printer" has 3 instances (three physical printers). Every process uses a resource in a strict three-step cycle.
A process must request a resource before using it and must
release it after finishing. Request via request() syscall or
wait() on a semaphore; use freely; release via release() or
signal(). Deadlocks happen when a process gets stuck in step 1 (request) and
never reaches step 3 (release) — while another process is stuck the same way holding what
the first one needs.
Deadlock Characterization — The Four Necessary Conditions
In 1971, Edward Coffman identified four conditions that must all hold simultaneously for a deadlock to be possible. Break any one, and deadlock cannot occur. This is the foundation of every deadlock-handling strategy.
🎮 Interactive — Watch a Deadlock Form Step by Step
Two processes, two resources. Each process needs both resources to finish. Watch how the wrong interleaving locks them both forever.
Resource-Allocation Graph (RAG)
Galvin's textbook uses a graphical tool called the Resource-Allocation Graph to describe deadlock. It's a directed graph G = (V, E) where:
• If the graph has no cycle, then no deadlock exists.
• If the graph has a cycle AND each resource type has only one
instance, then deadlock definitely exists.
• If the cycle involves multiple-instance resources, deadlock may or
may not exist — you need the detection algorithm to be sure.
🎮 Interactive — Build a Resource-Allocation Graph
Methods for Handling Deadlocks — Overview
Galvin lists three broad strategies plus one non-strategy that most commercial systems use.
Deadlock Prevention — Break the Four Conditions
| Condition to Break | How | Cost / Problem |
|---|---|---|
| Mutual Exclusion | Make resources sharable (e.g. read-only files, spooling for printers). | Impossible for intrinsically non-sharable resources like mutex locks or printers writing directly. |
| Hold and Wait | Either request all resources at once, or release everything before requesting more. | Low utilisation (resources held but not used) and possible starvation. |
| No Preemption | If a process holding some resources requests more that cannot be granted, forcibly take all its current resources. | Only works for state that can be saved and restored, like CPU or memory — not printers. |
| Circular Wait | Impose a total ordering on all resource types and require every process to request in increasing order. | Most practical. Widely used in production kernels. |
If every process acquires locks in the same global order (say mutex A always before mutex B), no circular chain can form. This one rule prevents 90% of production deadlocks and is the pattern followed in Linux, PostgreSQL, and most C++ codebases.
Deadlock Avoidance — The Safe State Concept
A state is safe if the system can allocate resources to each process in some order and still avoid deadlock. Formally, there must exist a safe sequence ⟨P1, P2, …, Pn⟩ such that for each Pi, the resources Pi can still request can be satisfied by the currently available resources plus resources held by all Pj with j < i.
An unsafe state may lead to deadlock, but doesn't have to. However, once you enter an unsafe state, the OS can no longer guarantee that deadlock will not occur. Avoidance algorithms are conservative — they reject requests that would put the system into an unsafe state even if deadlock is not certain.
The most famous implementation of avoidance is Dijkstra's Banker's Algorithm, which uses Available, Max, Allocation, and Need matrices to run a safety check before granting every resource request. We cover it in depth — with a fully interactive walkthrough of the classic 5-process, 3-resource example — in the next tutorial.
Deadlock Detection
If we don't prevent or avoid, we must detect. The detection algorithm scans the current state of the system periodically and answers a single question: is there a set of processes that can no longer make progress? It uses Available, Allocation, and the current Request matrix (what each process is asking for right now).
# Deadlock Detection Algorithm — for m resource types
Work = Available
Finish = [Allocation[i] == 0 for i in processes] # idle procs trivially "finished"
while exists i such that Finish[i] == false and Request[i] <= Work:
Work = Work + Allocation[i] # pretend process finishes and releases
Finish[i] = true
if exists i where Finish[i] == false:
return "DEADLOCK: processes with Finish[i]==false are deadlocked"
else:
return "NO DEADLOCK"
If every resource type has only one instance, we can collapse the Resource-Allocation Graph into a wait-for graph: an edge Pi → Pj means "Pi is waiting for Pj". A cycle in this simpler graph directly proves deadlock. Databases use exactly this technique.
Run detection frequently (every 10 seconds) → catches deadlocks fast but wastes CPU. Run rarely (once an hour) → cheaper but users notice the freeze. A common heuristic: run only when CPU utilisation drops below a threshold — a plunge in utilisation often signals many processes are blocked.
Recovery from Deadlock
Once detection reports a deadlock, we must break it. Galvin lists two families of solutions.
| Strategy | Trade-off |
|---|---|
| Kill all deadlocked processes | Clean but expensive — lots of work lost |
| Kill one at a time, re-check | Minimises loss but repeated detection cost |
| Pick lowest-priority victim | Preserves important work |
| Pick shortest-remaining | Least CPU wasted on restart |
| Pick fewest-resources-held | Frees the most for others |
| Concern | Solution |
|---|---|
| Which resources to preempt? | Minimise cost — pick cheap-to-restore ones |
| What to do with victim? | Roll back to a checkpoint state |
| Prevent starvation? | Cap number of times a process can be victim |
| Restore state after preemption? | Requires checkpointing infrastructure |
🎮 Interactive — Recovery by Process Termination
Comparison — Which Method to Choose?
| Method | When to Use | Overhead | Real-World Example |
|---|---|---|---|
| Prevention | Safety-critical systems | Design-time discipline | Airplane control software, medical devices |
| Avoidance | When max needs are known in advance | High — safety check per request | Real-time systems with known workloads |
| Detection | Long-running server processes | Periodic algorithm cost | Database systems (Oracle, PostgreSQL, MySQL) |
| Ignore | Consumer OS, deadlocks rare | Zero | Windows, macOS, Linux desktop |
Most general-purpose OSes use the Ostrich algorithm: ignore the problem unless the user complains. It's cheap and deadlocks are rare in desktop use. Databases, which see deadlocks often, use detection + rollback. Real-time systems that must never fail use prevention.
Real-World Applications
findDeadlockedThreads() at runtime. Profiler
tools like VisualVM highlight cycles in the object monitor wait graph.