CEUC301 · Unit 4 of 9

Deadlock Modeling &
System-Level Handling

Chapter 3 gave you real tools — semaphores, mutexes, monitors — for making threads wait safely for each other. This chapter is about what happens when "wait safely" goes wrong in a completely different way: not corruption, not a race, but a thread that will wait correctly, forever, for something that will never come.

Colour contract Linux — primary OS Windows — secondary OS process-level — a whole process, or a resource thread-level — one thread of WriteWell

The spine. WriteWell now protects two separate things with two separate locks: a document lock around the in-memory document buffer, and a log lock around the autosave log file. T_save needs both to finish an autosave: the document lock to read the current text, the log lock to record that it saved. T_flush, rotating the log file to keep it from growing forever, also needs both — log lock first, to do the rotation, then the document lock, to double-check the document matches what the log says was last saved. Same two locks. Opposite order. That's the whole chapter.

4.1 · FOUNDATIONS

Deadlock Modeling, Prevention & Avoidance

Two threads, two locks, opposite order — and neither one did anything wrong.

T_save locks the document, then asks for the log. T_flush locks the log, then asks for the document. If both get their first lock before either asks for the second, neither request can ever be granted. This isn't a bug in either thread's logic — each one, read alone, is completely correct. The problem only exists in how the two combine.

Two cooks, one knife, one cutting board

Imagine two cooks sharing a kitchen with exactly one knife and one cutting board. Cook A grabs the knife, Cook B grabs the board. Cook A now needs the board; Cook B now needs the knife. Both are perfectly willing to share — they'll hand over what they're holding the instant they're done with it — but neither will ever be done, because neither can start without the thing the other is holding. This is a deadlock: a set of threads, each waiting for a resource held by another thread in the same set, with no possible sequence of events that lets any of them proceed.

Modeling: the four conditions and the graph

Coffman, Elphick, and Shoshani proved in 1971 that all four of the following must hold simultaneously for a deadlock to exist. Break any one, and deadlock becomes structurally impossible — not just unlikely.

The four necessary conditions (Coffman conditions)
ConditionWhat it meansIn the spine
Mutual exclusionAt least one resource is held in a non-shareable way. Both locks are exclusive — that's the whole point of a lock.
Hold and waitA thread holds one resource while waiting for another. T_save holds the document lock while waiting for the log lock.
No preemptionA held resource can't be forcibly taken away; it's only released voluntarily.Neither thread's lock can be yanked away by the OS mid-hold.
Circular waitA cycle exists: each thread in the cycle waits for a resource held by the next.T_save → log lock → T_flush → document lock → T_save.

A resource-allocation graph (RAG) makes circular wait visible directly: processes and resources are nodes, an edge from a resource to a process means "held by," and an edge from a process to a resource means "wants." A cycle in this graph is the circular-wait condition, drawn.

Figure 1 · the cycle, drawn

T_save and T_flush, deadlocked
resource-allocation graph
T_save T_flush documentlock loglock wants wants solid = holds (assignment edge) · dashed = wants (request edge)
Reading the figure: follow the arrows in either direction and you return to where you started — that round trip is the deadlock. With only one instance of each resource (exactly the case here: one document lock, one log lock), a cycle in the graph doesn't just suggest deadlock, it guarantees it.
Depth — with multiple instances per resource, a cycle isn't a guarantee

If a resource type has more than one interchangeable instance (say, three identical log locks instead of one), a cycle in the RAG becomes necessary but not sufficient for deadlock — there might be a free instance of that resource type sitting elsewhere in the cycle that lets someone proceed after all. The guarantee above depends specifically on each resource in the cycle having exactly one instance, which is the common case for a lock but not for resources like "printers" or "database connections" in general.

Prevention: make one condition structurally impossible

Prevention attacks one of the four conditions so deadlock literally cannot arise, at some cost in flexibility or efficiency.

Attacking each condition
AttackHowCost
Eliminate mutual exclusionMake the resource shareable (spooling a printer, for instance).Doesn't work for anything that genuinely can't be shared — a write lock on a document being one.
Eliminate hold-and-waitRequire a thread to request everything it will ever need up front, atomically, before starting.Poor resource utilisation — T_save would hold the log lock the whole time it's editing the document, even during the part that never touches the log.
Allow preemptionLet the OS forcibly reclaim a held resource from a waiting thread. Complex, and not meaningful for every resource type — you can preempt a CPU (Chapter 2); you can't safely preempt "half-written to a log file."
Eliminate circular waitImpose a global order on resources; every thread must request them in that order.Cheap and the one actually used almost everywhere.

Applied to the spine: declare "document lock always before log lock," system-wide. T_save already follows this. T_flush is rewritten to acquire the document lock first too, even though it uses the log lock first logically — the acquisition order is what matters, not the order of use. With both threads obeying the same order, the cycle in Figure 1 can never form: whichever thread gets the document lock first is guaranteed to be able to get the log lock too, because the other thread can't have skipped ahead to grab it out of order.

Avoidance: allow the conditions, refuse unsafe requests

Prevention forecloses a condition permanently, everywhere. Avoidance is more surgical: allow all four conditions to exist in general, but before granting any single request, check whether granting it could still lead somewhere safe. Dijkstra's Banker's algorithm does exactly this, provided every thread declares its maximum possible need for each resource type up front.

The bookkeeping Allocation[i] = what thread i currently holds Max[i] = the most thread i will EVER need, all at once Need[i] = Max[i] − Allocation[i] (what it might still ask for) Available = Total − ∑ Allocation[i] (what's free right now)

A state is safe if there's some order to finish every thread one at a time, where each thread's remaining Need can be met from Available plus whatever earlier-finishing threads have released. A request is granted only if the resulting state is still safe — otherwise the requester waits, even though the resources it's asking for happen to be free right now.

Worked example: three resource pools, four residents

WriteWell's process now tracks three shared pools: network connections (9 total), disk I/O buffers (6 total), and worker-thread slots (7 total).

Current state: Allocation, Max, and derived Need
ThreadAlloc R1R2R3 Max R1R2R3 Need R1R2R3
T_save121532411
T_flush201423222
B_sync212624412
P_backup110331221

Available right now (Total − sum of Allocation): R1=3, R2=2, R3=3.

Running the safety algorithm: T_flush's need (2,2,2) fits in Available (3,2,3), so it can finish first, releasing (2,0,1) back — Available grows to (5,2,4). From there, B_sync (4,1,2) fits, then P_backup (2,2,1), then T_save (4,1,1) last. Safe sequence: T_flush → B_sync → P_backup → T_save. The state is safe.

Two requests against this same safe state
RequestCheckVerdict
T_flush asks for 1 more R1, 1 more R2 New Available (2,1,3) — re-running safety finds the same order still finishes everyone. GRANTED
T_save asks for just 1 more R2 New Available (3,1,3) — now no thread's Need fits: every Need vector needs more R2 than 1.DENIED

The second request looks tiny — one more disk buffer — and would still be well within T_save's own declared Need. It's refused anyway, because granting it leaves not one single thread able to finish with what remains. Nothing has deadlocked yet at the moment of the request; the Banker's algorithm's whole job is refusing to walk into a state where deadlock becomes inevitable, before it happens.

Figure 2 · try your own request

Same state as above — test any request
live safety check
Within declared Need?
Within current Available?
Resulting state
Pick a thread and a request, then click Check. Try T_save requesting (0,1,0) — the denied example above — or T_flush requesting (1,1,0), the granted one.
Pitfall

Banker's algorithm requires knowing every thread's maximum possible need in advance — a real, awkward requirement most real programs can't satisfy exactly. It's taught because the underlying safe/unsafe-state idea is foundational, not because operating systems commonly run it as-is; most real systems lean on prevention (lock ordering) or detection (Section 4.2) instead.

Practice 4.1

  1. Which single Coffman condition does "always acquire locks in the same global order" attack, and why does breaking just that one condition prevent deadlock even though the other three still hold?
    Show solution

    Circular wait. Mutual exclusion, hold-and-wait, and no-preemption can all still be true, but a global order means it's never possible for thread A to hold what thread B wants while thread B holds what thread A wants — whichever resource comes first in the order, some thread will always be able to get it uncontested, breaking any potential cycle before it can close.

  2. Variation. If T_save's Need were (4,1,1) but Available were only (3,1,3) with nothing else pending, could T_save finish right now?
    Show solution

    No — Need (4,1,1) is not ≤ Available (3,1,3) in the first component (4>3). T_save must wait, regardless of what any other thread is doing, until enough R1 becomes available.

  3. Interpretation. A system uses the Banker's algorithm and never deadlocks. Does that mean deadlock was impossible for this workload regardless of allocation policy?
    Show solution

    No — it means the avoidance policy successfully steered around every unsafe state it was offered. The same threads, with a naive first-come allocator instead, could easily deadlock. Avoidance doesn't mean deadlock was never possible; it means every request that would have led somewhere unsafe was refused.

  4. Synthesis. Connect prevention's "eliminate hold-and-wait" strategy back to Chapter 3's bounded buffer: would requiring T_save and T_flush to acquire all three semaphores at once (instead of one at a time) actually help here?
    Show solution

    It would eliminate hold-and-wait for this specific interaction (no thread would ever hold one semaphore while waiting for another), but at the same cost noted in the prevention table: T_save would hold mutex for its entire operation instead of just the few lines that touch the buffer, blocking unrelated access far longer than necessary. It solves the deadlock at the cost of a large chunk of Chapter 3's own concurrency benefit.

4.2 · RESPONSES

Detection & Recovery Trade-offs

Don't prevent it. Don't avoid it. Just catch it fast and clean up.

Section 4.1's approaches both cost something before a deadlock ever threatens: prevention gives up flexibility permanently, avoidance needs advance knowledge of every thread's maximum need. A third philosophy pays nothing upfront: let the four conditions hold freely, let deadlocks actually happen, and run a smoke detector.

Detection: collapse the graph, look for a cycle

Take Figure 1's resource-allocation graph and collapse each resource node away, replacing "process wants a resource held by another process" with a direct edge between the two processes. This wait-for graph makes the check almost trivial: T_save waits for T_flush; T_flush waits for T_save. A 2-node cycle, found by the same kind of cycle-detection a compiler uses to catch circular imports. For single-instance resources (true of both locks here), a cycle in the wait-for graph is exactly a deadlock — found after the fact, rather than refused in advance the way Section 4.1's Banker's algorithm does.

Recovery: now that you've found it, what do you do?

Process termination
  • Kill everyone in the cycle at once. Simple, certain to break the deadlock, and wastes every bit of work all of them had done.
  • Kill one at a time, re-checking after each. Less wasteful, but needs a way to pick who — by priority, by how much CPU time they've already invested, by how many resources they hold, or by how far from finished they are.
Resource preemption
  • Instead of killing a thread outright, forcibly take one of its resources back and roll it to an earlier checkpoint — needs the ability to checkpoint and restart cleanly, which not every resource or piece of state supports.
  • The same victim-selection problem as termination, plus a new one: if the same thread keeps getting picked as the victim every time, it may never actually finish — a direct route into Section 4.4's starvation.

The trade-off: how often do you even look?

Running the detection algorithm isn't free, and the choice of when to run it is its own balancing act, structurally identical to trade-offs already seen twice in this book: Chapter 1's context-switch-overhead curve and Chapter 2's scheduling quantum.

When to run detection
PolicyAccuracyCost
On every resource requestCatches a deadlock the instant it forms. Highest possible overhead — a full graph-cycle check on every single request, most of which were never going to deadlock at all.
On a fixed timerDeadlocked threads may sit stuck for up to one full interval before being noticed.Bounded, predictable overhead — the usual real-world choice.
When CPU utilisation drops unexpectedlyCheap heuristic trigger, but idle CPU has other innocent causes too (Chapter 1's I/O-bound threads, for one).Lowest overhead, least reliable trigger.
Pitfall

Detection-and-recovery does not mean "no cost." The cost simply moves from before the deadlock (Section 4.1's restricted flexibility or upfront bookkeeping) to after it (lost work from termination, detection-interval delay, or the engineering effort of implementing safe rollback). Choosing this philosophy is a bet that deadlocks will be rare enough that paying the recovery cost occasionally beats paying prevention's cost on every single request.

Practice 4.2

  1. In the wait-for graph for the spine deadlock, how many nodes and how many edges does the cycle have?
    Show solution

    Two nodes (T_save, T_flush) and two edges (each waiting for the other) — the smallest possible cycle. Collapsing the two resource nodes out of Figure 1's four-node RAG leaves exactly this.

  2. Variation. If recovery kills only T_flush, does T_save immediately proceed?
    Show solution

    Yes. Killing T_flush releases the log lock it was holding; T_save's pending request for that lock can now be granted, breaking the cycle with a single termination rather than requiring both threads to be killed.

  3. Interpretation. A system runs deadlock detection once every 30 seconds. A deadlock forms 2 seconds after a check. How long do the affected threads sit stuck before recovery even begins?
    Show solution

    Up to 28 seconds — the deadlock isn't found until the next scheduled check, roughly 30 seconds after the previous one, minus the 2 seconds that had already elapsed. This is exactly the fixed-timer policy's accuracy cost from the table above, made concrete.

  4. Synthesis. Explain why resource preemption's victim-selection problem is a direct preview of Section 4.4, before you've even read it.
    Show solution

    If victim selection always favours the same criteria (say, "always preempt whichever thread holds the fewest resources"), one unlucky thread matching that profile repeatedly could be rolled back every single time a deadlock involving it is detected, never actually completing — the system keeps making progress overall, just never for that one thread. That is precisely Section 4.3's definition of starvation, arrived at from the recovery side rather than the scheduling side.

4.3 · DISTINCTIONS

Deadlock vs. Starvation: The Resource Trade-off

Gridlock, where nobody moves — versus one car that never gets let into the lane.

Both look like "a thread that never gets what it's waiting for." They are not the same failure, they don't have the same fix, and conflating them is a reliable way to lose marks on exactly this kind of question.

The precise distinction
DeadlockStarvation
Who's stuckEvery thread in the deadlocked set. Possibly just one thread; everyone else is fine.
System progressNone — the deadlocked set can never advance. The system as a whole keeps working; one thread just never gets its turn.
Is it guaranteed?Yes, structurally — given the Coffman conditions, it's a certainty, provable from the resource-allocation graph. Usually a probabilistic consequence of a policy plus bad luck, not a structural guarantee.
Standard fixPrevention, avoidance, or detection + recovery (this chapter). Aging, or a fairness bound built into the policy itself (Chapter 2).

Where the two meet: allocation policy is the shared cause

Chapter 2 already showed this trade-off once, from the scheduling side: plain Priority scheduling can starve a low-priority thread indefinitely if higher-priority threads keep arriving, and the fix was aging — gradually raising priority the longer a thread waits. Resource allocation has the identical tension. A resource-granting policy based on priority, or on whichever request looks "safest" to grant (Section 4.1's Banker's algorithm doesn't rule this out), can systematically favour some threads over others. Nothing about that is a deadlock — the system keeps running, resources keep getting granted — but a consistently-deprioritised thread can wait indefinitely all the same.

Strict FCFS resource queuing is the opposite choice: grant requests in arrival order, full stop. This makes starvation structurally impossible (Chapter 2's Round Robin argument applies unchanged: bounded position in a queue is a bounded wait) at the cost of the flexibility priority-based or safety-based allocation offered — an urgent request can still be stuck behind a pile of older, less urgent ones.

Pitfall

"The system is deadlocked" and "this one thread is starving" call for different diagnosis and different fixes. If other threads are visibly still making progress, it is not a deadlock, no matter how long the stuck thread has been waiting — reach for aging or a fairness bound, not the resource-allocation graph.

Practice 4.3

  1. A monitoring dashboard shows one thread that hasn't run in ten minutes, while every other thread on the system is actively making progress. Deadlock or starvation?
    Show solution

    Starvation. Deadlock requires that no thread in the affected set can progress; here, everything else is fine, which rules out deadlock by definition regardless of how long the one thread has waited.

  2. Variation. Would switching that system's resource-granting policy from priority-based to strict FCFS fix the problem, and what would it cost?
    Show solution

    Yes — FCFS guarantees a bounded wait, eliminating this kind of starvation structurally. The cost is the same one Chapter 2 identified for Round Robin: every request is now served in arrival order regardless of urgency, so a genuinely time-critical request can be stuck behind a long queue of routine ones that arrived first.

  3. Interpretation. Does using the Banker's algorithm (Section 4.1) guarantee no thread will starve?
    Show solution

    No. Banker's algorithm guarantees the system never enters an unsafe state, which prevents deadlock — it says nothing about whether one particular thread's requests happen to keep landing in the "would be unsafe" category while others' don't. Avoidance and starvation-freedom are separate guarantees; a system can have one without the other.

  4. Synthesis. Explain, in terms of the Coffman conditions, why starvation doesn't need any of the four to hold.
    Show solution

    The Coffman conditions describe a structural configuration that makes progress impossible. Starvation doesn't require impossibility — it only requires that a policy keeps choosing not to grant a particular thread's request, even though granting it would be entirely possible and would not violate mutual exclusion, hold-and-wait, no-preemption, or circular wait in any way. It's a fairness failure, not a structural one.

4.4 · DISTINCTIONS

Starvation & Livelock

Blocked forever is one failure. Busy forever, going nowhere, is a different one.

Starvation, briefly revisited

Section 4.3 defined it: a thread indefinitely denied a resource while the rest of the system keeps moving. The fix is the same one Chapter 2 already used for Priority scheduling: aging — gradually raise a waiting thread's priority (or its position in a resource queue) the longer it waits, so it eventually outranks everything else and is guaranteed to get through.

Livelock: busy, active, and still stuck

Deadlock's threads are passive — blocked, using no CPU, waiting for something that will never come. Livelock is the active twin: threads keep changing state in direct response to each other, consuming real CPU time, and still make no forward progress. Two people in a hallway who both step left to let the other pass, then both step right for the same reason, forever, are livelocked — neither is standing still, and neither is getting anywhere.

Worked example: a deadlock "fix" that trades one bug for another

Suppose T_save and T_flush try to avoid Section 4.1's deadlock without imposing a lock order: each grabs its first lock, tries the second with a short timeout, and if that fails, releases what it's holding and retries after a fixed delay.

Both threads use the same fixed backoff delay (3 time units)
tT_saveT_flush
0locks documentlocks log
1tries log — blockedtries document — blocked
2both release their lock, both wait 3 time units
5locks documentlocks log
6tries log — blockedtries document — blocked
7both release, both wait 3 time units — identical to t=2

No thread is ever blocked for more than an instant, no cycle ever sits still long enough for Section 4.2's detector to even catch it — and neither thread ever finishes an autosave. The timeout-and-backoff fix genuinely solved deadlock (nothing waits forever) while introducing livelock (nothing finishes, either) in its place.

The fix: break the symmetry

The failure above depends entirely on both threads retrying in lockstep. Give each thread a randomised backoff delay instead of the same fixed one, and their retries stop colliding within a few rounds almost certainly — whichever one happens to wait slightly longer lets the other grab both locks and finish. This is exactly the idea behind exponential backoff with jitter, used everywhere from network protocols to database retry logic.

All three, side by side

Deadlock, starvation, and livelock compared
DeadlockStarvationLivelock
Thread stateBlocked, passiveBlocked/waiting, passive Actively running, busy
CPU usageNone, for the stuck threadsNone, for the stuck thread Real CPU time spent, continuously
System-wide progressNone, for the affected setYes, elsewhere None, for the livelocked threads
Standard fixPrevention, avoidance, detection+recoveryAging, fairness bound Randomised backoff, breaking symmetry
Pitfall

A system under livelock can look healthy on the crudest monitoring dashboards — CPU usage is high, threads are clearly "doing something." The tell isn't resource usage, it's the absence of actual completions over time. Always check throughput (Chapter 2: jobs completed per unit time), not just whether the CPU looks busy.

Practice 4.4

  1. Two threads retry a failed lock acquisition using the exact same fixed 100ms delay, forever. Deadlock, starvation, or livelock?
    Show solution

    Livelock — both threads are actively running (acquiring, failing, releasing, waiting, retrying) rather than blocked, and neither ever completes despite the constant activity.

  2. Variation. If only T_save retries with a fixed delay while T_flush retries with a randomised one, is livelock still likely?
    Show solution

    Much less likely. Livelock in this example depends on both threads' retries landing at the same instant, round after round; if even one side's timing varies randomly, their attempts will drift apart within a few rounds and one will eventually succeed while the other is still waiting.

  3. Interpretation. A support ticket says "the server's CPU has been at 95% for an hour, but no requests are completing." Which of this chapter's three failures does that best match, and why not the other two?
    Show solution

    Livelock. High, sustained CPU usage rules out deadlock (whose stuck threads are blocked, not running) and points away from simple starvation (which wouldn't typically drive CPU usage up across the board, only deny progress to specific threads while others complete normally) — active CPU use combined with zero completions is livelock's signature.

  4. Synthesis. Explain why aging (Section 4.3's starvation fix) would do nothing to solve the livelock in the worked example above.
    Show solution

    Aging works by eventually giving a waiting thread top priority so a scheduler or allocator is forced to favour it. In the livelock example, both threads already have equal standing and neither is being systematically deprioritised — the problem is purely timing symmetry between two equally-privileged threads, which raising one's priority over time doesn't address at all. Aging fixes an unfairness problem; livelock here is a synchronisation problem.

WRAP-UP

Cheat Sheet & Self-Test

Everything above, compressed to what you'd want on the way into an exam.

4.1 Modeling, prevention, avoidance

4 Coffman conditions (all needed): mutual exclusion, hold-and-wait, no preemption, circular wait.

RAG cycle + single-instance resources = guaranteed deadlock.

Prevention: break one condition, usually via lock ordering. Avoidance: Banker's algorithm — grant only if the resulting state is safe.

4.2 Detection & recovery

Wait-for graph = RAG with resources collapsed out. Cycle = deadlock, found after the fact.

Recovery: kill (all at once or one at a time) or preempt-and-rollback. Victim selection matters.

When to check: every request (accurate, costly) vs. timer (cheaper, delayed).

4.3 Deadlock vs. starvation

Deadlock: all stuck, no system progress, structurally guaranteed.

Starvation: one stuck, system progresses elsewhere, policy-driven not guaranteed.

Priority/safety-based allocation risks starvation; strict FCFS avoids it, at a flexibility cost.

4.4 Starvation & livelock

Starvation fix: aging.

Livelock: threads active, retrying, CPU busy — zero completions. Symmetric backoff can cause it; randomised backoff fixes it.

Tell: high CPU + zero throughput, not "CPU idle" (that's deadlock).

Mixed self-test

Deliberately not grouped by section — your exam won't be either.

  1. Name the four Coffman conditions in one line each.
    Show solution

    Mutual exclusion (a resource can't be shared), hold-and-wait (holding one resource while waiting for another), no preemption (a held resource can't be forcibly taken), circular wait (a cycle of threads each waiting on the next). (4.1)

  2. A resource-allocation graph has a cycle, but each resource type in the cycle has 3 interchangeable instances. Is deadlock guaranteed?
    Show solution

    No — with multiple instances per resource, a cycle is necessary but not sufficient. A free instance elsewhere in the cycle could still let a thread proceed, breaking the deadlock that a single-instance cycle would have guaranteed. (4.1)

  3. Under the Banker's algorithm, a request is within both the requesting thread's declared Need and the system's current Available. Is it automatically granted?
    Show solution

    No — those two checks are necessary but not sufficient. The request is only granted if the safety algorithm confirms the resulting state is still safe; this chapter's own worked example showed a request that passed both checks and was still denied. (4.1)

  4. What's the fundamental trade-off between detecting deadlocks on every resource request versus on a fixed timer?
    Show solution

    Checking every request catches a deadlock the instant it forms but pays the overhead of a full check on every single request, most of which were never going to deadlock. A fixed timer bounds that overhead but lets a deadlock sit unnoticed, for up to one full interval, before recovery even begins. (4.2)

  5. Every other thread on a system is completing work normally except one, which hasn't run in an hour. Deadlock or starvation?
    Show solution

    Starvation — deadlock requires that no thread in the affected set can progress; here, everything except one thread is fine, which rules deadlock out by definition. (4.3)

  6. Does switching a resource-granting policy to strict FCFS eliminate starvation for free?
    Show solution

    It eliminates starvation (a bounded queue position is a bounded wait, the same argument Chapter 2 made for Round Robin) but not for free: it costs the flexibility a priority-based or safety-based policy had, since an urgent request now has to wait behind every older request regardless of urgency. (4.3)

  7. A server shows 95% CPU utilisation but zero completed requests over the last hour. Which failure mode does this match, and what rules out the other two?
    Show solution

    Livelock. High CPU usage rules out deadlock, whose stuck threads are blocked and consume no CPU; zero completions system-wide (not just for one thread) rules out simple starvation, where everything else keeps completing normally. (4.4)

  8. Two threads livelocked on a symmetric fixed-delay retry loop are given independently randomised delays instead. Does this guarantee they'll never collide again?
    Show solution

    No guarantee, but it makes repeated collision extremely unlikely rather than certain. Randomised backoff breaks the systematic symmetry that made every retry collide; two random delays could still coincidentally match on any given round, just not indefinitely, round after round, the way two identical fixed delays do. (4.4)

Further reading

  • Stallings, Operating Systems: Internals and Design Principles, 9th ed., Ch. 6. This course's primary text; closest match to this chapter's structure across all four sections.
  • Silberschatz, Galvin & Gagne, Operating System Concepts, 10th ed., Ch. 8. The standard reference treatment of the resource-allocation graph and Banker's algorithm.
  • Coffman, E.G., Elphick, M., & Shoshani, A., "System Deadlocks," ACM Computing Surveys, 1971. The original paper naming the four necessary conditions used throughout Section 4.1.
  • Dijkstra, E.W., "EWD108: Een algorithme ter voorkoming van de dodelijke omarming" ("An algorithm to prevent the fatal embrace"), 1965. The original description of what became the Banker's algorithm.

Before this chapter — Chapter 2 (priority scheduling's starvation risk and its aging fix, reused directly in Section 4.3) and Chapter 3 (the mutexes and locks this entire chapter's deadlocks are built from — every scenario here is Chapter 3's tools, used correctly in isolation, colliding in combination).

Where this goes next — Unit 5, Memory Management System Design: memory fragmentation, paging, segmentation, and the kernel allocators (buddy, slab) that hand out memory itself. Every lock this chapter protected guards some region of memory — Unit 5 asks how the OS decides where that memory physically lives in the first place.