AiTechWorlds
AiTechWorlds
Two approaches to preventing gridlock in a city:
Approach 1 — Prevention: Change the road rules permanently. Make all one-way streets. Prohibit left turns at certain intersections. These constraints eliminate the conditions that create gridlock — at the cost of less efficient routes.
Approach 2 — Avoidance: Keep current road rules, but install a smart traffic management system. Before allowing a car onto an intersection, the system checks whether doing so could lead to gridlock. If yes, it holds the car back until it is safe.
Operating systems use the same two approaches to deadlock — and a third one: wait for deadlock to occur, then recover.
Idea: Structurally eliminate one of the four Coffman conditions. If any condition cannot hold, deadlock is impossible.
Make all resources shareable. Read-only resources (like read-only files) are naturally shareable and cannot cause deadlock.
Limitation: Some resources are inherently non-shareable — a printer printing two jobs simultaneously produces garbage. Mutual exclusion cannot always be eliminated.
Option A: Require each process to request all resources at once before it starts. Either it gets everything or it gets nothing.
Problem: A process may not know all resources it needs before starting. Acquiring all resources upfront leads to poor utilization — a process holds resources it may not need for hours.
Option B: Require a process to release all held resources before requesting new ones.
Problem: Progress and data integrity issues — releasing a half-written database record corrupts data.
Allow the OS to forcibly take resources from a process when needed by another.
Works for: CPU (context switch), memory pages (swap to disk)
Does not work for: Printers (mid-job preemption ruins the printout), database locks (preempting a lock mid-transaction corrupts data)
Impose a global linear ordering on all resource types. Assign each resource type a unique number. Require that every process acquires resources in strictly increasing order of their assigned numbers.
R1 (network socket) = 1
R2 (file lock) = 2
R3 (database lock) = 3
Rule: always acquire in order 1 → 2 → 3. Never request a lower-numbered resource while holding a higher one.
If all processes follow this ordering, circular wait becomes mathematically impossible. This is the most practical prevention strategy and is used in kernel lock ordering rules (Linux, Windows kernel developers follow strict lock acquisition orders).
Cost: Processes must be programmed to follow the ordering. Ordering may not match natural access patterns.
Idea: Allow normal resource requests, but before granting any request, simulate the result and check whether the resulting state is safe. Grant the request only if the system remains in a safe state.
Safe state: A state where there exists at least one sequence in which all processes can complete without deadlock.
Dijkstra named this after bank lending: a bank only lends money if it can guarantee it can satisfy all clients' maximum possible demands from its available reserves.
Given: n processes, m resource types.
Data structures:
Max[n][m]: maximum demand of each processAllocation[n][m]: currently allocated resourcesAvailable[m]: currently available resourcesNeed[n][m] = Max − Allocation: remaining needAvailable: A=3, B=3, C=2
| Process | Allocation (A,B,C) | Max (A,B,C) | Need (A,B,C) |
|---|---|---|---|
| P0 | 0,1,0 | 7,5,3 | 7,4,3 |
| P1 | 2,0,0 | 3,2,2 | 1,2,2 |
| P2 | 3,0,2 | 9,0,2 | 6,0,0 |
Safety Algorithm:
Trace:
Wait — let me re-check with the standard example values. Using the classic Silberschatz textbook example:
Available: A=3, B=3, C=2
| Alloc A,B,C | Max A,B,C | Need A,B,C | |
|---|---|---|---|
| P0 | 0,1,0 | 7,5,3 | 7,4,3 |
| P1 | 2,0,0 | 3,2,2 | 1,2,2 |
| P2 | 3,0,2 | 9,0,2 | 6,0,0 |
| P3 | 2,1,1 | 2,2,2 | 0,1,1 |
| P4 | 0,0,2 | 4,3,3 | 4,3,1 |
Safety trace:
Safe sequence: P1 → P3 → P4 → P0 → P2
The system is in a safe state. Any resource request is approved only if the resulting state remains safe.
If prevention and avoidance are not used, the OS must periodically detect deadlock and recover.
For systems with single-instance resources, build a wait-for graph: nodes are processes, edges represent "is waiting for." Deadlock exists if and only if the graph contains a cycle.
P1 ──waits──→ P2 ──waits──→ P3 ──waits──→ P1 (cycle = deadlock)
Run cycle detection (DFS) periodically. Cost: O(n²) for n processes.
When deadlock is detected, the OS must break it:
Process Termination:
Resource Preemption:
| Approach | Condition Broken | Overhead | Practical Use |
|---|---|---|---|
| Prevention — Hold & Wait | Hold and Wait | Low utilization | Simple embedded systems |
| Prevention — Circular Wait | Circular Wait | Coding discipline | OS kernels, DBMS |
| Prevention — Preemption | No Preemption | Data integrity risk | CPU scheduling only |
| Avoidance — Banker's | All (safe state check) | High runtime overhead | Rarely used in practice |
| Detection + Recovery | None prevented | Periodic overhead | Database engines |
| Ignore (Ostrich) | None | Zero | Linux, Windows user space |
Deadlock handling is a spectrum from strict prevention to blissful ignorance:
Most production systems use a combination: kernel code follows strict lock ordering (circular wait prevention), database engines use detection and recovery, and user-space application deadlocks are handled by watchdog timers or user restarts.
Get this course's notes on Telegram!
Free cheat sheets, summaries & practice exercises