Deadlocks: Prevention, Avoidance, Detection, and Recovery
10.1 The Deadlock Problem and the Resource Model
Hook: Every process in a running system must wait for resources from time to time, and waiting is normal. But what happens when a whole set of processes is blocked because each one holds a resource and waits for another resource that someone else in the set holds? Nobody can move, nothing can finish — that is a deadlock. Before any prevention, avoidance, or detection scheme makes sense, we need a precise picture of the problem and of the resources it involves.
10.1.1 What a Deadlock Is
A deadlock is the situation where a set of processes are all blocked because each one holds a resource and waits for another resource that a different process in the set currently holds. Nobody can release what the others need, so nobody can move. The standard textbook phrasing is the same idea in formal clothes: 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 same set — typically, the release of a held resource.
The canonical setup goes like this. Take two resources, R1 and R2, and two processes, P1 and P2. R1 has been allotted to P1, so P1 is holding it, and R2 is held by P2. Now P2 needs R1, and P1 needs R2. Each process waits for the other to release the resource it wants so it can acquire it. That mutual waiting is the deadlock. If you draw this as a graph — P1 waiting for R2 (assigned to P2), P2 waiting for R1 (assigned to P1) — you get a cycle, and a cycle of this kind is the signature of a deadlock.
Intuition — the two-marker standoff: Picture two colleagues at a whiteboard. A holds the red marker and needs the black one; B holds the black marker and needs the red one. Neither will put theirs down first, so nothing gets written — forever. Each person's holding is a resource; each person's "I need the other's" is the wait. The relationship is a circle, and a circle of mutual waiting is exactly what the graph picture shows. Where the analogy breaks: people could negotiate and agree on a swap, but a blocked process cannot negotiate — it can do nothing at all until the resource is granted to it, which is why the operating system has to manage this by design.
The same pattern shows up in the real world with devices. Two processes can each be holding a device and be in need of the other one's device. Each waits for the other to release its device so it can be acquired. Nothing ever proceeds, because both releases are conditional on receiving what the other holds.
The deadlock need not involve two different resource types. It can also form among instances of one type: suppose three processes each hold one of three CD-RW drives, and each process now asks for a second drive. Every process is waiting for a release that can only come from another waiting process — deadlock again, with a single resource type.
10.1.2 Resource Types and Instances
Deadlock only matters when the resources in the system are finite in number. If resources were infinite, we would never need to think about deadlock at all. In a real system the resource set is finite: memory space, the CPU, files, and I/O devices such as printers, monitors, and DVD drives are all examples.
Within this model we distinguish a resource type from its instances. A resource type is the category — a monitor, a printer, or a CPU. If there are three printers in the lab, then the resource type "printer" has three instances. In general, with resource types and each type having some number of instances, we can describe the whole system as a matrix of counts: which instance of which type is held by which process, and which instances are free. The algorithms we follow for detection and avoidance are built on exactly this picture.
Two cautions about defining types. First, the instances of one type must be interchangeable: if the ninth-floor printer and the basement printer are not equivalent to the users, they belong in separate resource classes. Second, a process cannot ask for more instances than the system has — a process cannot request three printers when the lab has only two.
10.1.3 How a Process Uses Resources
To use a resource, a process follows a fixed three-step routine:
- Request — the process asks for the resource. If it is not available, the process waits until it can acquire it.
- Use — the process operates on the resource (for example, prints on the printer).
- Release — the process gives the resource back.
Remember this order: the process has to request the resource before using it, and it has to release the resource after using it. All of the deadlock analysis that follows — prevention, avoidance, detection, recovery — assumes this request-use-release discipline. In the operating system these steps are system calls: request() and release() for devices, open() and close() for files, allocate() and free() for memory, and wait() and signal() on semaphores for resources the kernel does not manage.
Pitfalls:
- Treating ordinary waiting as a deadlock. Waiting is normal and safe; a deadlock is waiting that can never be satisfied, because the processes that would release the resources are themselves waiting.
- Confusing resource types with instances. The type "printer" is one category; three identical printers are three instances of it. Every graph and every algorithm in this lecture counts instances, so mixing the two up breaks all later calculations.
- Assuming a deadlock needs many processes or many resource kinds. Two processes and two resources are enough, as the R1/R2 example shows.
Visual intuition: sketch the two-process deadlock as a four-node diagram. Draw P1 and P2 as circles, R1 and R2 as squares with one box each (one instance per type). Draw the assignment edges from the box of R1 to P1 and from the box of R2 to P2 (P1 holds R1, P2 holds R2), then the request edges P1 → R2 and P2 → R1. Now follow the arrows: P1 → R2 → P2 → R1 → P1 — you return to where you started. That closed loop is the deadlock fingerprint. One-sentence takeaway: a directed cycle of wait-and-hold edges is the signature of a deadlock, and the whole rest of this lecture is about preventing, avoiding, detecting, or breaking that fingerprint.
Recap: a deadlock is a closed circle of mutual waiting over finite resources, and every process uses resources in the same fixed order — request, use, release. With this model in hand, the next step is to pin down exactly which conditions must hold for such a circle to form.
Real-world and domain connection: the same standoff appears at every scale of real systems. The classic historical example is the Kansas legislature law: "When two trains approach each other at a crossing, both shall come to a full stop and neither shall start up again until the other has gone" — a guaranteed deadlock written into law. In modern software, deadlocks show up in database transactions (two transactions each hold a row lock and ask for the other's row), in multithreaded programs that take mutex locks in opposite orders, and in distributed systems. That is why every operating systems course teaches the same model: a handful of finite resource types, their instances, and the request-use-release discipline — the vocabulary for all the analysis that follows.
10.2 The Four Necessary Conditions for Deadlock
Four conditions characterize a deadlock. All four must hold at the same time. They are called the characterization of deadlocks, and you should not forget them: mutual exclusion, hold and wait, no preemption, and circular wait.
10.2.1 Mutual Exclusion
Mutual exclusion means that only one process at a time can use a resource. If two printers are held by a particular process, then at that moment only that process can use them. No other process — say P2 or P3 — can use either printer until that process releases one or both of them. If a resource is non-shareable, only the process currently holding it may use it. For deadlock, what matters is that the resource at the center of the conflict is non-shareable: shareable resources, such as a read-only file, can be used by many processes at once and never force anyone to wait, so they can never take part in a deadlock.
10.2.2 Hold and Wait
Hold and wait means a process is holding at least one resource and is waiting to acquire additional resources that are currently held by another process. This is the condition we saw in the earlier diagram: the holding process sits on its resource while it waits for someone else's resource. The important word is and: the process both keeps what it already has and asks for more. A process that released everything before waiting would not satisfy this condition.
10.2.3 No Preemption
No preemption means a resource is released by a process voluntarily, and only by it. Whatever process is holding a resource has to release it on its own, after completing its task. This point is very, very important: the CPU cannot simply relinquish a process from a resource; that cannot be done. The process must voluntarily release the resource after finishing its work. Note that "preemption" here means taking a held resource away — it is not about CPU scheduling. A printer or a file in use cannot simply be yanked from its holder and handed to someone else.
10.2.4 Circular Wait
Circular wait is the chain that closes the loop. If we start from P0 and end back at P0, the pattern is: P0 waits for a resource held by P1, P1 waits for a resource held by P2, P2 waits for a resource held by P3, and so on, until waits for a resource held by P0. This is called a circular wait. If this pattern is present in any graph, then we can say there is a deadlock.
10.2.5 Necessary, Not Sufficient — and Characterizations Are Not Tools
All four conditions must happen simultaneously. If all four hold together, the deadlock has settled in; if even one is missing, we cannot call it a deadlock. But hold on: the four conditions are a necessary condition for deadlock, not a sufficient one. That distinction matters.
A common mistake is to write the characterizations when asked for something else. The characterizations are the four conditions above; they are not the tools. A tool is a mechanism — the way we avoid, prevent, or detect a deadlock. If we find a mechanism that prevents deadlock, that mechanism is a tool, not a characterization. Keep the two apart: the characterizations say what a deadlock is; the tools — prevention protocols, avoidance algorithms such as the Banker's algorithm, detection algorithms, and recovery mechanisms — say what we do about it.
Warning — the exam trap (called out in the lecture): when a question asks for a tool, do not answer with the four conditions. When it asks for the characterizations, do not describe the algorithms. The four conditions are necessary but not sufficient: they say when a deadlock can exist, not when one must exist.
Worked example — the conditions in real code. Two threads share two mutex locks, first_mutex and second_mutex. Thread 1 acquires first_mutex, then second_mutex; thread 2 acquires second_mutex, then first_mutex. If thread 1 grabs first_mutex while thread 2 grabs second_mutex, then: mutual exclusion holds (each mutex admits one holder), hold and wait holds (each thread holds one lock and waits for the other), no preemption holds (a lock is only released by its owner), and circular wait holds (thread 1 waits for what thread 2 holds, and vice versa). All four at once — the threads are deadlocked. If either thread had finished before the other started, no deadlock would occur; the four conditions are necessary, and all four must coincide in time.
Pitfalls:
- Writing the characterizations when a question asks for the tools, or the tools when it asks for the characterizations — the lecture's explicitly flagged mistake.
- Thinking the four conditions are fully independent. Circular wait already implies hold and wait: a closed chain of waiting processes can only exist if each process keeps its resources while waiting. That is why breaking one condition is enough — the conditions overlap, and any single break destroys the combination.
- Believing that any one condition, by itself, is a deadlock. A circular wait with preemption available is not a deadlock; a hold-and-wait with a shareable resource is not a deadlock. Only the simultaneous combination qualifies.
Visual intuition: picture the four conditions as four switches that must all be on for the deadlock "machine" to run. Each switch is a property of the system: exclusivity of the resource, retention while waiting, no forced release, and a closed waiting chain. If any switch is off, the machine cannot start. The deadlock itself is the fourth switch — circular wait — closing; the first three just make that closure possible.
Recap: a deadlock requires four conditions at once — mutual exclusion, hold and wait, no preemption, and circular wait — and they are necessary but not sufficient. And remember the vocabulary rule: the four conditions are the characterizations, never the tools. With the conditions named, the next section builds the graph that lets us see the circular wait.
Real-world and domain connection: these four conditions are the checklist every real system uses when diagnosing a stuck system. Database deadlock reports, thread dumps from server runtimes, and distributed lock managers all end up asking the same four questions: was the resource exclusive, did the holders keep it while waiting, could anything force a release, and is there a closed waiting chain? If the answer is "yes, yes, no, yes", the diagnosis is a deadlock — and the cure will be one of the strategies in the next sections.
10.3 Resource Allocation Graphs
10.3.1 Notation and Edge Types
The resource allocation graph (RAG) is a way to draw the state of processes and resources. The vertices come in two kinds: circles for processes and squares for resource types. Inside each square, small boxes represent the instances of that resource type; the number of boxes inside the square is the number of instances.
There are two kinds of edges. A request edge goes from a process to a resource, , meaning process has requested resource . The arrow touches the square itself, not one of the instance boxes, because the resource has not been allocated yet. An assignment edge goes from an instance to a process: the arrow starts at one of the boxes inside the square and ends at the circle of the process that holds that instance. Only when an arrow comes from a specific instance box to the process circle can we say that instance is held by that process.
Worked example — three processes, four resource types. Draw a graph with three processes and four resource types where R1 and R3 have a single instance each, R2 has two instances, and R4 has three. In the first diagram, suppose every instance of every resource type is allocated to some process: each assignment arrow leaves an instance box and touches a process circle, and the only request edges are P1 → R1, P2 → R3, and P3 → R2 — wait, the exact point is that no request edge participates in a cycle. A cycle means you can start from a particular place, follow the arrows, and end at the same place you started. In that diagram you can wander along arrows, but you always stop somewhere new — you never return to the starting point — so there is no cycle, and therefore no deadlock.
10.3.2 The Cycle Rule
The next diagram changes the story: P3 is requesting R2, and R2 is currently held by P1 and P2. Now a cycle can be traced — start from P1, go to P2, come to P3, and end back at P1 by following the arrows. This gives us the rules you have to remember:
- If the graph contains no cycle, there is no deadlock. This point is very important.
- If the graph contains a cycle, and every resource type in the cycle has only one instance, then there is a deadlock. With single instances, a cycle is both a necessary and a sufficient condition for the existence of a deadlock. Only one instance — very, very important.
- If the graph contains a cycle but some resource type has several instances, then there may or may not be a deadlock. Some other process might release one of its instances, and that instance could then be allocated to the process that is requesting it; in that case the deadlock never materializes. So with multiple instances, a cycle is a necessary condition for deadlock but not a sufficient one.
You can see the contrast in a graph that contains two cycles — one that starts with P1 and ends with P1, and another that starts with P2 and ends with P2. If the involved resource types are single-instance, there is a deadlock. In a different diagram a cycle is present but no deadlock exists, because the number of instances is more than one: for example, if P4 is going to release a particular instance, that instance may be allocated to P3, and then the waiting chain breaks. The presence of a cycle only creates the chance of a deadlock; it does not force one.
Worked example — a cycle with no deadlock (multi-instance). Picture a small graph: P1 holds an instance of R1 and waits for R2; P3 waits for R1; a fourth process P4 holds another instance of R2. Follow the arrows: P1 → R1 → P3 → R2 → P1 is a genuine closed loop, so a cycle exists. Yet there is no deadlock: P4 holds an instance of R2 that it can release at any moment, that free instance can be handed to P3, and the waiting chain breaks. The cycle existed; the deadlock never materialized. This is the contrast the exam likes: with one instance per type a cycle is decisive; with several instances it is only a danger sign.
Exam note: the single-instance rule — cycle in the graph is necessary and sufficient — and the multiple-instance rule — cycle is necessary but not sufficient — are favorite question points. Do not forget them.
Pitfalls:
- Reading a request edge as an allocation. A request edge points at the square; only an arrow that starts at a specific instance box is an allocation.
- Announcing a deadlock from any cycle. With multi-instance types the cycle is only a necessary condition — check the instance counts first.
- Forgetting what a cycle is: you must be able to leave a vertex along an edge and return to it by following arrows. If every walk stops somewhere new, there is no cycle, and rule 1 says the system is deadlock-free.
Visual intuition: think of the graph as a map of arrows. Circles and squares are the landmarks; request edges run into squares, assignment edges run out of instance boxes into circles. The landmark to hunt for is any closed walk that returns to its starting vertex. When you find one, the single-instance case makes the verdict immediate — deadlock — while the multi-instance case leaves an open question: can some other process release the instance that would break the loop?
Recap: the resource allocation graph turns the deadlock question into a graph question — no cycle means no deadlock, a cycle with single instances means deadlock, a cycle with several instances means "maybe". This same graph returns later wearing claim edges, as a deadlock-avoidance tool.
Real-world and domain connection: resource allocation graphs are not just a textbook drawing. Lock checkers in kernels and deadlock detectors in databases construct exactly these wait-for relationships — who holds what, who waits for whom — and hunt for cycles in real time. The single-instance rule is why database row-lock detectors can declare a deadlock the moment a cycle appears, while allocators over pools of identical buffers, where several instances of a kind exist, must run the heavier multi-instance machinery.
10.4 The Three Strategies for Handling Deadlocks
10.4.1 Prevention and Avoidance versus Detection and Recovery
There are three broad ways to handle deadlocks. The first is deadlock prevention or avoidance: we make sure the system will never enter the deadlock state. The second is detection and recovery: we allow the system to enter the deadlock state, detect that it has happened, and then recover from it. The third is to ignore the problem as if it had never occurred. This is very important: prevention and avoidance aim at never entering the deadlock state, while detection and recovery accept that the deadlock can happen and deal with it afterwards.
| Strategy | Does the deadlock ever form? | Information needed | Main cost |
|---|---|---|---|
| Prevention | No — one of the four conditions is stopped in advance | None about future requests | Low device utilization, reduced throughput |
| Avoidance | No — every request is screened before granting | A priori: each process declares its maximum demand | Runtime safety checks on every request |
| Detection + recovery | Yes — the system lets it happen, then finds and fixes it | Allocation and request state, plus a detection run | Detection overhead and recovery losses (aborts, rollbacks) |
| Ignore | Yes — the system behaves as if it could not happen | None | An occasional crash or manual restart when a deadlock actually occurs |
Prevention and avoidance differ in one decisive way: prevention just stops a condition from ever being true, while avoidance uses declared maximums to check each allocation against the future. The "when to pick which" rule of thumb: prevention and avoidance suit systems where deadlock is expensive; detection and recovery suit systems where deadlock is possible but rare; and ignoring is what most real operating systems do, because they treat deadlock as too rare to justify constant protection.
10.4.2 Ignoring the Problem
Ignoring the deadlock is the last way of handling it. In real-time systems the possibility of a deadlock is pretty much small — it may happen something like once in a year or two — so this case is often handled by simply acting as if the problem had not occurred and going ahead. We will return to this point at the end, because it matches what real organizations actually do.
Ignoring is not carelessness; it is an economic choice. Prevention, avoidance, and detection all run constantly and therefore cost constantly, while an undetected deadlock may cost almost nothing for months at a time. If a deadlock does arrive, the system degrades — held resources sit idle, waiting processes pile up — until an operator notices and restarts it. That restart is the de facto "recovery" of the ignore strategy.
Recap: three strategies — stop it before it happens (prevention or avoidance), let it happen and then fix it (detection and recovery), or pretend it never happens (ignore). The next sections build the first two in detail, and the last section of the lecture circles back to why practice leans on ignoring.
Real-world and domain connection: most production operating systems, including UNIX and Windows, offer no general deadlock-prevention machinery; the platform assumes applications avoid deadlock on their own and deals with the consequences when they do not. Database systems, in contrast, cannot afford to ignore the problem: they detect deadlocked transactions and abort one of them automatically. The three strategies are not competing theories — they are a toolkit, and each system picks the combination that fits its cost of failure.
10.5 Deadlock Prevention
The idea of prevention is to make sure at least one of the four necessary conditions never holds. Since all four conditions are needed for a deadlock, if any one of them is knocked out, the deadlock cannot occur. Note the difference from avoidance, which we will see next: prevention does not need any a priori information. In advance, we do not know how many resources each process will need, and that is fine — we just stop one of the conditions from ever being true.
10.5.1 Breaking Mutual Exclusion
Mutual exclusion can be attacked directly: resources are either shareable or non-shareable. For non-shareable resources we must keep mutual exclusion, but for shareable resources it is not required. A read-only file is the classic example: any number of processes can use it at the same time. Only when something is being written to a file do we need exclusion. So by designing resources to be shareable where possible — for instance, read-only files — the mutual exclusion condition simply does not occur, and that condition of the deadlock is prevented.
The honest limit: most resources cannot be shared. A printer cannot serve two processes at once, and only one process may update a file at a time. So breaking mutual exclusion is the cheapest prevention where the resource allows it, and it is off the table for intrinsically non-shareable resources — which is why the other three conditions each need their own protocols.
10.5.2 Breaking Hold and Wait
To break hold and wait, we must guarantee that a process requests a resource only when it is not holding any other resource. Two protocols achieve this:
- Protocol 1 — request everything up front: every process requests and is allocated all the resources it will ever need before it starts its execution. If P1 wants R1 and R2, both must be allocated before execution begins. If only R1 is allocated, the problem of hold and wait appears: P1 has R1 and is in need of R2, which is exactly the condition we are trying to prevent.
- Protocol 2 — release before asking again: a process may request a resource only when it holds no resource at all. It uses the resource, releases it, and only then asks for the next one.
Following either protocol prevents hold and wait, but the cost shows up in utilization. Whichever protocol we follow, resource utilization will always be lower. If P1 is in need of R1 and R2, we either give it all the resources or none. If we give everything to P1, P2 may not get R1 or R2 and has to stop. If we give nothing, then R1 or R2 is not used properly while P1 sits waiting. Either way some resource goes unused, and that problem cannot be avoided.
Worked example — copying, sorting, printing. Take a process that must copy data from a DVD drive to a file on disk, sort the file, and print the result. Under protocol 1, the process requests the DVD drive, the disk file, and the printer all at once — and then sits on the printer for the entire run, even though it needs the printer only at the end. Under protocol 2, it requests the DVD drive and disk file, copies, releases both, then re-requests the disk file and the printer, prints, and releases. Both protocols prevent hold and wait, and both pay for it: protocol 1 pins the printer for the whole job; protocol 2 forces the process to release and re-request, so it may wait between phases. Both also risk starvation — a process that needs several popular resources can wait indefinitely, because at least one of its resources is always held by someone else.
10.5.3 Breaking No Preemption
To break no preemption, we force preemption to happen. Two protocols are commonly used:
- Protocol 1 — take the held resources back: if a process holding some resources requests another resource that cannot be satisfied immediately, we take the held resources away from it. Suppose P1 holds R1 and R2 and asks for R3, but the request cannot be satisfied until R1 and R2 are released. We forcibly take R1 and R2 out of P1 — even though P1 has not completed its execution — add them to the list of available resources, and later let P1 restart afresh by requesting all the resources it needs again.
- Protocol 2 — preempt from a waiting process: when a process requests a resource that is currently held by a process which is itself waiting for something else, we preempt the resource from that waiting process. It is not doing any work anyway. If P1 is holding R1 and waiting for R2, we can take R1 away from P1 and allocate it to P2 or P3, whichever is requesting it. Why should R1 sit with P1 when P1 is blocked?
Either protocol makes preemption occur, and by doing so prevents the deadlock. These are some of the protocols in use; there may be others, but these two show the pattern. Note the scope: preemption is practical for resources whose state can be saved and restored — CPU registers and memory space — and not for printers or tape drives, whose state cannot be rolled back.
10.5.4 Breaking Circular Wait
Circular wait is broken by imposing a total ordering on the resource types: resources must always be allocated to processes in increasing order of enumeration. Each resource type gets a number, and a process may request resources only in that increasing order.
An example walks through it: suppose a particular process wants a file, a disk drive, and a printer. The natural order of access is first the disk drive, then — using the disk drive — the file, and then sending the file to the printer. So numbers are assigned in that same order: the disk drive gets the smallest number, the file next, the printer last. The process must request the disk drive first, then the file, then the printer. If every process requests in increasing order of enumeration, the circular wait condition cannot arise — the cycle has no way to close.
Why the ordering kills the cycle: suppose a circular wait did exist, with waiting for a resource held by , waiting for one held by , and so on around the ring. Under the ordering rule, holding and asking for forces at every step, so — a number that is less than itself, which is impossible. Since the contradiction cannot hold, the circular wait cannot form.
The price again is low device utilization and reduced system throughput: some resources may not be used, or may wait longer than necessary before being used. But if we need to prevent the deadlock, this ordering is one way to do it.
Pitfalls:
- Believing the ordering by itself is enough. The system can impose the rule, but applications must actually follow it; lock-order verifiers (such as the witness tool in FreeBSD) exist precisely because programs request locks out of order.
- Assuming preemption works for every resource. Forcing preemption on a printer or tape drive can destroy state; the two preemption protocols are meant for saveable resources.
- Forgetting the utilization tax: every prevention protocol costs utilization and throughput — the exam likes asking what prevention gives up in exchange for safety.
Recap: prevention knocks out one of the four conditions — share the resource, all-or-nothing requests, force preemption, or impose a fixed request order — and needs no knowledge of future requests. The price is always the same: lower utilization. When that price is too high, the next strategy, avoidance, uses a priori information to allow more flexibility while still refusing unsafe allocations.
Real-world and domain connection: the ordering trick is the workhorse of lock ordering in real code — databases and multithreaded servers order their locks by address or by table identifier so every transaction acquires them in the same sequence. The no-preemption protocols appear in memory management, where a process's memory can be saved to disk and handed to another process, and in kernel locking code that releases a spinlock before waiting on a second one. The witness lock-order verifier on FreeBSD is a live example of the ordering rule enforced by software at runtime.
10.6 Deadlock Avoidance: Safe and Unsafe States
10.6.1 A Priori Information
If the deadlock cannot be prevented, the next line of defense is avoidance: make sure the system never enters the deadlock state — that is, never enters an unsafe state. Avoidance needs something prevention does not: a priori information. Each process must declare, in advance, how many resources it is going to request — its maximum demand. With that information in hand, we can allocate resources and check whether the allocation keeps the system in a safe state.
The difficulty is that we cannot know the future requests and releases of the processes. Sometimes a process acquires a resource and never uses it; sometimes we cannot tell whether a resource will be required at all. Prediction is hard. But based on the declared maximums we can describe the resource allocation state — how many resources there are, how many have been allocated, and what the maximum demand of each process is for each resource — and check whether the state is safe.
10.6.2 Safe States, Unsafe States, and Deadlocks
A state is safe when there exists a sequence of processes such that there are enough resources for the first process to finish; as soon as it finishes it releases its resources, which are enough for the second process to finish its execution; and so on until every process has finished. The sequence need not be the order in which the processes arrived — the order may be different, but some sequence must exist. Formally: a sequence is a safe sequence for the current state if each can get everything it still needs from the currently available resources plus the resources held by the processes that finished before it.
If a process requests a resource that is not immediately available, it waits until some other process completes. That waiting does not by itself mean a deadlock. Avoidance simply ensures the system stays in a safe state.
Three facts follow, and they are basic facts to understand:
- If the system is in a safe state, there are no deadlocks.
- If the system is in an unsafe state, the possibility of a deadlock is there — which does not mean there is a deadlock.
- All deadlocks are unsafe states, but not all unsafe states are deadlocks. An unsafe state may still be rescued if the processes happen to release their resources in a helpful order; a deadlock is the worst case where that rescue fails.
We have to make sure the system never enters the unsafe state. That is the whole job of avoidance.
The choice of algorithm depends on the instance count. If each resource type has a single instance, we can use the resource allocation graph with claim edges, which we have already seen. If there are multiple instances, we have to go for the Banker's algorithm.
Worked example — twelve tape drives. Suppose the system has twelve tape drives and three processes: P0 declares a maximum of ten, P1 a maximum of four, P2 a maximum of nine. At time t0, P0 holds five, P1 holds two, P2 holds two — so three drives are free. Is the state safe? Yes: the sequence works. P1 needs at most two more drives and can finish immediately, returning its two drives (now five free); P0 can then take its five remaining drives, finish, and return all ten; P2 then takes its seven remaining drives and finishes. Every need is satisfiable in order. Now grant P2 one more drive at time t1: only two drives are free. P1 can still finish and return its two (four free), but then P0 still needs five more and P2 still needs six more — neither can proceed, and if both ask, the system deadlocks. That single granting decision — "yes" to P2's request — is exactly the decision avoidance is meant to catch.
Scope — what avoidance assumes: avoidance lives on the declared maximums. If a process lies about its maximum, or if the number of instances changes while the system runs, the safety check is built on sand. Avoidance also costs: a request that could have been granted may be refused because granting it would enter an unsafe state, so resource utilization is lower than with no protection at all.
Visual intuition: picture the state space as three nested regions. The largest region holds all states. Inside it sits the smaller region of unsafe states. Inside that sits the deadlock states. Safe states lie in the ring outside the unsafe region: no deadlock is possible there, and the system can always reach a state where every process finishes. Entering the unsafe ring does not force a deadlock — some paths through it still reach completion — but the system no longer controls its fate; the processes do. The avoidance algorithms in the next two sections are trip wires that refuse to step out of the safe ring.
Recap: avoidance is prevention with knowledge — each process declares its maximum, and the system grants only allocations that keep the state safe. Safe states are deadlock-free; unsafe states are only dangerous; all deadlocks are unsafe, but not every unsafe state is a deadlock. Next: the two avoidance tools, one for single-instance systems and one for multi-instance systems.
Real-world and domain connection: the safe/unsafe distinction is the same idea that database schedulers apply with lock tables and that resource managers apply in virtualization platforms — never commit to an allocation unless a completion order still exists. The tape-drive example is the textbook classic because it shows in one paragraph how a safe state can turn unsafe on a single grant, which is the entire reason avoidance algorithms exist.
10.7 Avoidance with Single-Instance Resources: Claim Edges
10.7.1 The Claim Edge
For avoidance, the resource allocation graph gains a third kind of edge. Besides the request edge (process to resource) and the assignment edge (instance to process), we now draw claim edges. A claim edge is a dashed or dotted line from a process to a resource, and it records a priori information: this process may request that resource in the future. It has not requested it yet, but it might. Whatever resources a process must claim, it should claim them a priori in the system so that avoidance has something to work with.
In the example: resource R1 is allocated to P1 — an assignment edge. P2 is requesting R1 — a request edge. Dashed claim edges from both P1 and P2 toward R1 show that both may be in need of this resource in the future; we do not know yet.
10.7.2 Edge Conversions and the Granting Rule
The edges are not static; they convert into each other as the system moves:
- A claim edge converts into a request edge whenever the process actually requests the resource. The dashed line turns solid.
- A request edge converts into an assignment edge whenever the resource has been allocated to the process.
- When the process releases the resource, the assignment edge converts back into a claim edge, because in the future the process may request the same resource again — we do not know.
The rule for granting a request is the cycle check. A request may be granted only if converting the claim edge to a request edge does not create a cycle in the graph. If the conversion would create a cycle, granting would let the system enter an unsafe state, so the request is not granted. This is the resource allocation graph method for deadlock avoidance, and it works for the single-instance case. If a problem gives a resource type with several instances — say A has four instances, B has three, C has two — this graph method will not work, and we have to switch to the Banker's algorithm.
Worked example — the cycle test in action. Take two single-instance resource types, R1 and R2. P1 holds R1 (assignment edge) and has declared a claim on R2 (dashed edge P1 → R2). P2 has declared claims on R1 and R2, and now actually requests R2, which is currently free. Availability alone would say "grant it" — the resource is free. But converting P2's claim edge to a request edge, and then to the assignment R2 → P2, closes the loop P1 → R2 → P2 → R1 → P1, using P1's claim on R2 and P2's claim on R1. A cycle exists, so the request is refused: granting it would move the system into an unsafe state. This is why the graph method refuses free resources — a grant now can set up a deadlock later, and the claims make that future visible in advance.
Pitfalls:
- Granting on availability instead of safety. A resource can be free and still be refused, because the grant would form a cycle. Availability answers "can we?", the cycle check answers "may we?".
- Forgetting the conversion chain — claim → request → assignment → claim again on release. Each transition is the vocabulary of the algorithm.
- Using the graph method with multi-instance types. With several instances per type the cycle test is not decisive, and the graph method simply does not apply — that is what the Banker's algorithm is for.
Visual intuition: the graph is the same map as before — circles for processes, squares for resources, one box per instance — now decorated with dashed spokes (claims). Each dashed spoke says "this process might ask for this resource." A request becomes safe exactly when turning the relevant dashed spoke solid closes no loop. Watch the graph as requests arrive: the allowed grants are precisely those that leave every walk able to end somewhere new.
Recap: claim edges bring a priori knowledge into the resource allocation graph, and requests are granted only when their edge conversion creates no cycle. That handles avoidance for single-instance systems — the multi-instance case needs the Banker's algorithm, the subject of the next section.
Real-world and domain connection: the claim-edge idea maps directly onto lock declarations in concurrent programming — a thread announces the locks it may take, and a runtime or static analyzer refuses the transition that would close a wait-for loop. The cycle test is also the same check that graph-based deadlock detectors run, which is why the single-instance rule keeps coming back: the graph encodes everything the scheduler needs to know.
10.8 The Banker's Algorithm
The Banker's algorithm handles deadlock avoidance when resource types have multiple instances. Under this algorithm, each process must claim the maximum use of the resources in advance. Whenever a process requests a resource, it may have to wait; and once a process gets all its resources, it must return them within a finite amount of time — after some point it has to release them. One honest note: the Banker's algorithm is not a very powerful algorithm, but it is the standard one we use to avoid a deadlock in this setting.
Why the name? The idea comes from banking: a banker must never lend out cash in a way that leaves it unable to honor every customer's declared need when that customer asks for it. The algorithm applies the same principle to resources: never allocate in a way that could leave some process unable to finish.
10.8.1 The Data Structures
The algorithm works on four data structures. Let there be processes and resource types.
- Available: a vector of length giving how many instances of each resource type are present (free) right now. Available of type is written . If , then instances of resource type are free.
- Max: an matrix giving the maximum demand of each process for each resource type. is the maximum demand of process for resource type — the most may ever ask for. This is the a priori declaration.
- Allocation: an matrix giving what has been allocated to each process so far. is how many instances of process currently holds.
- Need: an matrix giving what each process still needs to complete. is how many more instances of process requires.
These four vectors and matrices are all we need to run the algorithm. Each row, such as or , is treated as a vector, and a statement like means the comparison holds component-wise: every entry of the need vector must be at most the corresponding entry of the work vector.
10.8.2 Computing Need
The Need matrix is not given; we compute it from Max and Allocation. For each process and each resource type:
The verbal rule is "max minus allocation gives the need of each instance of a resource with respect to each process." If you know how to solve this problem from a snapshot, you can solve any question that is given: the only thing that changes is whether some incoming request can be satisfied — that is, whether it keeps the system in a safe state.
Worked example — the snapshot and its Need matrix. Five processes, P0 to P4, and three resource types, A, B, and C, with 10, 5, and 7 instances respectively. The snapshot is taken at some time t0:
| 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) |
| P3 | (2,1,1) | (2,2,2) | (0,1,1) |
| P4 | (0,0,2) | (4,3,3) | (4,3,1) |
Available is (3,3,2). Each Need cell is max minus allocation: for P0, 7 minus 0 is 7, 5 minus 1 is 4, 3 minus 0 is 3, giving (7,4,3); for P1, (3−2, 2−0, 2−0) = (1,2,2); and likewise for every other row: P2 needs (9−3, 0−0, 2−2) = (6,0,0); P3 needs (2−2, 2−1, 2−1) = (0,1,1); P4 needs (4−0, 3−0, 3−2) = (4,3,1). Sense-check: the allocations sum to (7,2,5), and adding the Available vector (3,3,2) gives (10,5,7) — exactly the installed counts of A, B, and C. The numbers are consistent.
10.8.3 The Safety Algorithm
The safety algorithm decides whether a state is safe. It is carried out for any number of processes to check whether the system is in a safe state.
- Let Work = Available, and let Finish[i] = false for all processes . Finish[i] means "process i has completed and returned its resources." Initially nothing has completed.
- Find an index such that Finish[i] == false and . The comparison is component-wise: every entry of the need vector must be less than or equal to the corresponding entry of work. If no such exists, go to step 4.
- and Finish[i] = true. Then go back to step 2 and look for the next process.
- If Finish[i] == true for all , the system is in a safe state. Otherwise it is not.
The narration that goes with it: work always starts equal to available; finish is false for all processes. We take the first process and check whether its finish is false, then check the condition need less than or equal to work. If the condition holds, we can allocate those instances to the process, complete its work, and the resources it held — its allocation — have to be returned. We add the allocation back to work and mark the process finished. Then we check the next process. If a process's need is not less than or equal to work, we leave it aside and try others; that is why the order of selection matters.
Always try to select a process with a small need, so that a safe sequence can be formed: if we finish small-need processes first, the available pool grows and can later be allocated to a process that needs more resources. That is the main idea.
The update rule and its twin form. The lecture computes the work update as "subtract the need, then add back the maximum the process holds":
This is exactly the same as the standard textbook form
because always — need is defined as max minus allocation. Use whichever form is easier; the numbers come out identical.
The safety check costs about operations: for each of the (at most ) selection rounds we may scan up to processes, comparing components each time. That is why the algorithm is a background check, not something run on every instruction.
10.8.4 Worked Example: Finding a Safe Sequence
Start from the snapshot above. Work = Available = (3,3,2) and Finish = (false, false, false, false, false).
Full trace. Round 1: P1's need (1,2,2) is less than or equal to work (3,3,2) — yes, because 1 ≤ 3, 2 ≤ 3, and 2 ≤ 2. Allocate the need, run P1 to completion, and return its maximum: work becomes (3,3,2) − (1,2,2) + (3,2,2) = (5,3,2), and Finish[P1] = true.
Round 2: P3's need (0,1,1) is less than or equal to work (5,3,2) — yes. Work becomes (5,3,2) − (0,1,1) + (2,2,2) = (7,4,3). Finish[P3] = true. First subtract the need, then add the maximum — that is how the availability is found.
Round 3: P4's need (4,3,1) is less than or equal to work (7,4,3) — yes. Work becomes (7,4,3) − (4,3,1) + (4,3,3) = (7,4,5). Finish[P4] = true.
Round 4: P0's need (7,4,3) is less than or equal to work (7,4,5) — yes. Work becomes (7,4,5) − (7,4,3) + (7,5,3) = (7,5,5). Finish[P0] = true.
Round 5: P2's need (6,0,0) is less than or equal to work (7,5,5) — yes. Work becomes (7,5,5) − (6,0,0) + (9,0,2) = (10,5,7). Finish[P2] = true.
All finishes are true, so the system is in a safe state. The safety algorithm finds the safe sequence: P1, then P3, then P4, then P0, then P2. Other sequences may also put the system in a safe state — that is also correct — but the sequence must be proper: at every step the chosen process's need must fit inside the current work. Sense-check: with every process finished, work has grown back to the full (10,5,7) — every instance is accounted for.
Exam note: this is the computation to practice — given allocation, max, and available, compute need, run the safety algorithm, and report a safe sequence.
10.8.5 The Resource-Request Algorithm
The safety algorithm alone handles the static question. The resource request algorithm handles a live request: when some process requests resources right now, can the request be satisfied? Let the request vector be Request[i]. The steps:
- If Request[i] Need[i], go to step 2; otherwise raise an error — the process is asking for more than it declared. Remember this very important rule: a process cannot request resources beyond its maximum; the request must stay within its need.
- If Request[i] Available, go to step 3; otherwise the process must wait.
- Pretend to allocate the requested resources to : modify the state as if the request were granted:
Then run the safety algorithm on this pretend state. If the resulting state is safe, the request is granted and the pretend state becomes the real one. If the resulting state is unsafe, the process has to wait, and the pretend changes are rolled back.
The request must be within the need and within the availability; only then can the request be satisfied, and only when the safety check passes. Whether the system is safe decides whether we grant or restore and wait.
10.8.6 Worked Example: A Request That Is Granted
Suppose P1 requests (1,0,2). Check the two conditions against the original snapshot:
- Request (1,0,2) Need[P1] = (1,2,2) — yes.
- Request (1,0,2) Available = (3,3,2) — yes.
Both conditions hold, so we pretend to allocate and modify the state:
- Available becomes (3,3,2) − (1,0,2) = (2,3,0).
- Allocation[P1] becomes (2,0,0) + (1,0,2) = (3,0,2).
- Need[P1] becomes (1,2,2) − (1,0,2) = (0,2,0).
Granting check. Now run the safety algorithm on the new state. Work = (2,3,0). P1's need (0,2,0) is less than or equal to (2,3,0), so Work becomes (2,3,0) + (3,0,2) = (5,3,2). Then P3: (0,1,1) (5,3,2), Work = (7,4,3). P4: (4,3,1) (7,4,3), Work = (7,4,5). P0: (7,4,3) (7,4,5), Work = (7,5,5). P2: (6,0,0) (7,5,5), Work = (10,5,7). All finishes true — the pretend allocation keeps the system safe, so the request is granted. The sequence we can follow is again P1, P3, P4, P0, P2.
10.8.7 Worked Example: A Request That Must Wait
Now suppose P4 requests (3,3,0). Check the conditions:
- Request (3,3,0) Need[P4] = (4,3,1) — yes.
- Request (3,3,0) Available = (3,3,2) — yes.
Both conditions pass, so we pretend to allocate:
- Available becomes (3,3,2) − (3,3,0) = (0,0,2).
- Allocation[P4] becomes (0,0,2) + (3,3,0) = (3,3,2).
- Need[P4] becomes (4,3,1) − (3,3,0) = (1,0,1).
Refusal check. Run the safety algorithm on the pretend state. Work = (0,0,2). Check every process: P1 needs (1,2,2) — not (0,0,2). P2 needs (6,0,0) — no. P3 needs (0,1,1) — no. P4 needs (1,0,1) — no. P0 needs (7,4,3) — no. No process can finish, so the resulting state is unsafe. Because the pretend state is unsafe, the request cannot be granted; P4 must wait, and the old state is restored. After allocating, the system should be safe — that is very important, and here it is not.
The same procedure would be applied to any other request, such as one from P0: check it against need and available, pretend to allocate, run the safety algorithm, and grant only if the result is safe. Note that a request may be refused even when the resources are available — P0 asking for (0,2,0) in the granted state would pass both checks and still be refused, because the state it leads to is unsafe.
Pitfalls:
- Granting on the two checks alone. Passing Request Need and Request Available is not enough; the safety run on the pretend state decides. Exam trick questions stop after the two checks and forget the pretend run.
- Forgetting to restore. A refused request must roll the state back completely — available, allocation, and need — or every later check runs on poisoned numbers.
- Mixing up the matrices. Request is compared against Need first (the process's own ceiling), then against Available (the system's pool); the wrong order or the wrong matrix is a classic error.
- Thinking "safe" means "no process waits". A safe state allows waiting; it only guarantees that some completion order exists.
Recap: the Banker's algorithm refuses any allocation that would take the system out of the safe region — check the request against need and availability, pretend to allocate, run the safety algorithm, and grant only a safe outcome. Its price is that processes may wait even when resources are free: the same utilization tax as prevention, now paid with a priori information instead of blanket restrictions.
Real-world and domain connection: the Banker's algorithm is the standard textbook avoidance method for multi-instance systems, but it is not a very powerful algorithm and is rarely what production systems actually run; its real value is in understanding safe and unsafe states. Its direct descendants are credit limits in database schedulers and admission control in systems where a maximum demand per client is known — the pattern of "never commit to a request whose future completion you cannot guarantee" shows up wherever a system hands out scarce resources with promises attached.
10.9 Deadlock Detection
Prevention and avoidance stop deadlocks from happening. Detection starts from the opposite assumption: the deadlock may already exist. We detect whether a deadlock has occurred, and if it has, we rectify it. Two tools, chosen by the instance count: for single instances the method uses the wait-for graph; for multiple instances it uses the detection algorithm.
10.9.1 Single-Instance Resources: The Wait-For Graph
For detection with single instances we build a wait-for graph, a variation of the resource allocation graph. The nodes are the processes only — no resource nodes — and an edge means process is waiting for a resource that is currently allocated to process . The whole point is to remove the resources: when a deadlock has occurred, we should be able to tell from the processes alone which one is waiting for which. If an arrow comes from one process to another, that first process is in need of a resource currently allocated to the second.
The graph is produced by converting the resource allocation graph: wherever an edge runs from to a resource held by , draw directly, so the converted graph holds edges only between processes. An edge exists in the wait-for graph exactly when the resource allocation graph contains the two edges and for some resource .
Worked example — building the wait-for graph. Start from a resource allocation graph and collapse it process-to-process:
- P1 requests R1, currently allocated to P2, so P1 points to P2.
- P2 requests R3, currently allocated to P3, so P2 points to P3.
- P3 requests R5, currently allocated to P4, so P3 points to P4.
- P4 requests R2, currently held by P1, so P4 points to P1.
The wait-for edges are P1→P2, P2→P3, P3→P4, and P4→P1. Now check whether a cycle exists: start at P1 and follow the arrows — P1→P2→P3→P4→P1 returns to the starting vertex. A cycle is found, and since every resource type in the example has a single instance, the wait-for graph tells us the system is in a deadlock: P1, P2, P3, and P4 are all deadlocked.
Then we check whether a cycle exists. If the wait-for graph contains a cycle, there is a deadlock. Detecting the cycle costs operations — "n squared" — where is the number of vertices, and the vertices are always the processes. With four processes, that is operations.
10.9.2 Multiple-Instance Resources: The Detection Algorithm
When resource types have several instances, we go back to vectors and matrices — availability, allocation, and request — and run the detection algorithm. It is a variation of the safety algorithm:
- Let Work = Available. For each process : if — meaning some resources are still allocated to it — set Finish[i] = false; otherwise set Finish[i] = true. A process holding nothing does not need to wait for anything to return.
- Find an index such that Finish[i] == false and Request[i] Work. If no such exists, go to step 4.
- ; Finish[i] = true; go back to step 2.
- If Finish[i] == false for some process , the system is in a deadlock state, and the processes with Finish[i] == false are the deadlocked ones.
The narration: work equals available; start checking each process. When a process has completed its work, it returns its resources; if it has not returned them, its allocation is not zero and its finish stays false. Every time we check whether the finish is false and then whether the request is less than or equal to work. If the condition holds, we add the availability to the allocation — that is, the process completes and its resources join the pool — and mark it finished. Then we check the next process. After all checks, if some finish vector entries are still false, the system is in a deadlock state. Remember: Finish[i] should be true for all processes. Even if one process has finish false, that process is deadlocked — it is waiting for a resource.
The optimistic assumption. When the algorithm finds a process with , it immediately reclaims the process's allocation — assuming the process needs no more resources and will soon finish. If that assumption is wrong, a deadlock may develop later, but it will be caught the next time the detection algorithm runs. That is why the algorithm works with Request (what processes want right now) instead of Need (what they declared up front): detection has no a priori declarations to trust.
When to run detection. Detection costs something every time it runs, so the schedule matters: invoke it when a resource request cannot be granted immediately (which catches the deadlock at birth and can name the requesting process), or at fixed intervals — say once per hour, or whenever CPU utilization drops below 40 percent, since a deadlock eventually cripples throughput and starves the CPU.
10.9.3 Worked Example: Detection with No Free Instances
The example differs from the Banker's one in the instance counts: here A has 7 instances, B has 2, and C has 6. Again there are five processes, P0 to P4, with allocation, request, and availability vectors. The key fact: there is no availability of any resource type — Available = (0,0,0) — every instance is allocated. The state is:
| Process | Allocation (A,B,C) | Request (A,B,C) |
|---|---|---|
| P0 | (0,1,0) | (0,0,0) |
| P1 | (2,0,0) | (2,0,2) |
| P2 | (3,0,3) | (0,0,1) |
| P3 | (2,1,1) | (1,0,0) |
| P4 | (0,0,2) | (0,0,2) |
Note that P2 is making a request for one instance of resource type C — request (0,0,1) — one more than it was asking before. We take the processes in some order — it can be any order, for example P0, P2, P3, P4, P1 — and check each one; the aim is only to check all of them. For a request to be satisfied, the condition Request[i] Work would have to hold. Consider P2: its request (0,0,1) against work (0,0,0) — the third component needs 1 ≤ 0, which is false, so the request cannot be granted and Finish[P2] stays false. The same happens for every process that has a request: even if some other process asks for any resource, it cannot be granted, because there are no free instances.
Full trace. Work = (0,0,0). P0's request is (0,0,0) — every component is zero, so it is less than or equal to work, and P0 is not waiting on anything: Work becomes (0,0,0) + (0,1,0) = (0,1,0) and Finish[P0] = true. Now P2's request (0,0,1) ≤ (0,1,0)? The C component says 1 ≤ 0 — no. P3's request (1,0,0) ≤ (0,1,0)? The A component says 1 ≤ 0 — no. P1's request (2,0,2) ≤ (0,1,0)? No. P4's request (0,0,2) ≤ (0,1,0)? The C component says 2 ≤ 0 — no. No further process can complete, so Finish stays false for P1, P2, P3, and P4 — those four are deadlocked. The system is in a deadlock state: there are insufficient resources to fulfill the processes' requests. We have detected the deadlock — P1 to P4 are the deadlocked processes.
Exam note: when the request cannot be satisfied — request is not less than or equal to available — that process's finish stays false; after checking every process, the ones still false are deadlocked. Know when to use the wait-for graph (single instance) versus the detection algorithm (multiple instances).
Recap: detection starts from "the deadlock may already exist": single-instance systems collapse the resource allocation graph into a wait-for graph and look for a cycle in time; multi-instance systems run the detection algorithm — work starts as available, and any process left with Finish false is deadlocked. Detection locates the deadlock; recovery (the next section) decides what to do with it.
Real-world and domain connection: wait-for graphs are exactly what database systems maintain as they track row locks — when a cycle appears among transactions, one transaction is chosen and aborted. The multi-instance detection algorithm is the model for detecting buffer and memory-pool deadlocks in kernels. The detection schedule matters in practice too: databases run detection on every lock wait, while batch systems check periodically, trading CPU cost against how long a deadlock is allowed to strangle the system.
10.10 Deadlock Recovery
Once a deadlock is present, we have to recover from it. There are two families of recovery: process termination and resource preemption.
10.10.1 Process Termination
Terminating processes is one way to recover. There are two versions:
- Abort all the deadlocked processes. In the detection example above, that would mean killing all of P1 to P4. Simple, but drastic: every partial computation of those processes is thrown away, and the work must be redone later.
- Abort one process at a time. Suppose we abort only P1; P2 and P3 are still there, so we have to check whether the cycle still exists. If the cycle disappears, we need not terminate P2, P3, or P4. If it does exist, we abort another process and check again. Each abort costs a fresh detection run, so this version is cheaper in damage and more expensive in overhead.
The next question is the order in which we abort. The choice depends on several criteria, and the first one is the priority of the process. If a process's priority is high, we should not choose that process to abort; we consider some other process. Then we look at how long the process has been executing and how long it will still take to complete. Then how much of the resources the process has used, and how much more it needs. Then how many processes will be terminated because of this one. Finally, whether it is an interactive process or a batch process. Based on these conditions we choose the order of aborting.
Q: When we abort processes one at a time, should the order follow bottom to top or top to bottom? A: No fixed direction — it is up to you; the choice follows the criteria we listed. Suppose there are four processes, P1 to P4, and P3 takes 12 minutes to complete while the other processes take less time, but the priority of P3 is high. In that case you should not choose P3 first; you have to consider P1, P2, or P4. Even if a process has already executed for a long time, the priority still comes first — that high-priority process is kept for some time while we check the others.
Q: So priority is the first point to be taken care of in process termination? A: Yes. Remember the convention: the highest priority is the lowest integer — priority number one is the highest priority. So a process with a high priority, like P3, will not be considered for aborting first. Whichever process has the lower priority is aborted first, irrespective of how long it has completed, or how much longer it takes to complete, or whatever resources it is using — that is not bothered about. Priority comes first; then the execution time, the resources used and needed, and how many more processes need to be terminated; the check of whether it is an interactive or batch process comes last.
The same priority idea appears everywhere, even in networking. In computer networks you have differentiated service classes: every message or process sent is tagged with a class, and based on that class it gets priority. When messages travel from source to destination they cross many nodes, and at each node many messages arrive and form a queue. The higher-priority message is sent first because it should not stay in the queue; it leaves the queue first, while the other processes wait in the queue. So priority decides the order there too — the same principle as choosing which deadlocked process to abort.
10.10.2 Resource Preemption, Rollback, and Starvation
The other family of recovery is resource preemption: we voluntarily take a resource away from a process. Three things have to be handled.
First, select a victim — decide from which process the resource is to be taken. The same ordering rules apply as in process termination: if a process has a high priority, it should not be selected as the first victim. From the lower-priority processes the resources are preempted, and the deadlock is resolved.
Second, rollback. Suppose R1 is currently held by P1 and we take R1 from P1. P1 cannot continue, because it has not completed its execution — if it had completed, it would have returned the resource itself. So we have to start it again after some time, restarting it from the state where it was terminated, or from the point where it rolled back. Sometimes, if the operation is an atomic action, it has to start from the very beginning and complete again. We do not always know whether the action is atomic or not, which is part of the difficulty.
Third, starvation. If we take R1 from P1 once, we must make sure starvation does not occur — but we cannot give that guarantee. P1 should not always be the victim; P1 should not starve always. Can we give that guarantee? No, that also cannot be guaranteed — no such guarantee exists. If we always take R1 from P1, or R2 from P1, or R3 from P1 — whatever P1 is holding — then P1 will be starving. Each of these recovery ways has its own problem that cannot be avoided entirely, but we still use them to recover.
Pitfalls:
- Forgetting that priority is the first abort criterion, not execution time or resources used. The convention is that the lowest integer is the highest priority, so a priority-1 process is never the first victim.
- Assuming rollback is free. A preempted process cannot continue; it restarts from a safe checkpoint, and atomic operations must restart from the very beginning.
- Believing starvation can be prevented. There is no guarantee that the same process is never the victim again; real systems fold the number of rollbacks into the victim cost so the same process is not picked repeatedly.
Real-world: this is why, in real-time systems and in organizations, deadlock rarely occurs — something like once in a year or two — because the event is so rare, and organizations tend to ignore the deadlock as if it had not occurred. That is the true sense of what happens. The prevention, avoidance, detection, and recovery machinery is the theory; the ignore strategy is what practice leans on because the event is so rare.
To recap the tool selection: for detection with single instances, use the wait-for graph; for detection with multiple instances, run the deadlock detection algorithm and find out which processes are deadlocked. For avoidance with multiple instances, use the Banker's algorithm; for avoidance with single instances, use the resource allocation graph. Keep all these points in mind — a problem based on them is likely.
Recap: recovery comes in two families — terminate processes (all at once, or one at a time with a cycle check after each, choosing victims by priority first) or preempt resources (select a victim, roll the process back, and accept that starvation cannot be guaranteed). And when the event is rare enough, the cheapest recovery of all is to ignore the deadlock and keep going.
Real-world and domain connection: the priority-first abort policy is standard in real-time systems, where missing a deadline costs more than losing a process, and in databases, where the transaction with the fewest locks or the youngest start time is usually the chosen victim. The networking parallel is direct: differentiated service classes tag every message, and routers forward higher-priority classes first — the same rule that decides which deadlocked process is aborted first. The whole recovery toolbox matters most in mission-critical servers; in everyday systems, the once-a-year deadlock is met with a manual restart.
Exam Guidance Summary
- Expect a problem on deadlocks — "you will have, sure, you may get a problem based on which you have to solve." Practice the end-of-chapter deadlock problems from the textbook; try solving them yourself, and if you cannot, bring them up in the next class.
- Master the computations: given a snapshot (allocation, max, available), compute the need matrix as max minus allocation, run the safety algorithm to produce a safe sequence, and test incoming requests with the resource-request algorithm, granting only when the pretend state is safe. The full arithmetic pipeline — need matrix, safety run, request check, refusal with restore — is the single most practiced skill in this lecture.
- Memorize the four necessary conditions — mutual exclusion, hold and wait, no preemption, circular wait — and remember they are necessary but not sufficient, and that characterizations are not the tools.
- Remember the cycle rules: no cycle means no deadlock; a cycle with a single instance per resource type means deadlock (necessary and sufficient); a cycle with multiple instances is only necessary, not sufficient.
- Remember the safe-state facts: safe state means no deadlock; unsafe state means only a possibility; all deadlocks are unsafe states, but not all unsafe states are deadlocks.
- Know the algorithm choice: single instance → resource allocation graph with claim edges for avoidance, wait-for graph for detection; multiple instances → Banker's algorithm for avoidance, detection algorithm for detection. Wait-for graph cycle detection costs O(n²) for n processes.
- Know the recovery criteria order: priority first (lowest integer is the highest priority), then how long the process has executed, how much longer it needs, resources used and needed, how many processes will be terminated, and interactive versus batch last.
- Quiz: it covers the topics of module 4 and module 5 — the two important topics covered so far. One attempt is allowed, with no timeline, and it must be completed before the 28th. Inform your classmates.
- In the next contact session, the solutions of both the regular and the makeup exam papers will be discussed.
Key Industry Applications
- Real-world: most real systems and organizations face deadlocks so rarely — roughly once in a year or two — that they use the ignore strategy: they act as if the deadlock problem has not occurred and keep going. The elaborate prevention and detection machinery is mostly insurance for rarer, mission-critical cases.
- Real-world: priority-driven process selection in deadlock recovery mirrors differentiated service classes in computer networks: at every node, messages of higher-priority classes are forwarded first so they do not linger in queues, while lower-priority messages wait.
- Real-world: shareable resources in practice — such as read-only files that any number of processes may read at once — remove the mutual exclusion condition without any scheduling machinery, which is the cheapest possible prevention.
- Real-world: the resource model itself is built from everyday finite resources: memory space, the CPU, files, printers, monitors, and DVD drives are the instances that the deadlock algorithms count and allocate.
- Real-world: the Banker's algorithm is the standard textbook avoidance method for multi-instance systems, but it is not a very powerful algorithm and is rarely what production systems actually run; its real value is in understanding safe and unsafe states.
- Real-world: database systems are the one place where detection and recovery are never skipped — lock-wait cycles among transactions are detected and one transaction is aborted automatically, which is the practical form of the wait-for graph and the abort-one-at-a-time recovery from this lecture.
OS Lecture 10 notes · Deadlocks: Prevention, Avoidance, Detection, and Recovery
Sections Breakdown
What a deadlock is, resource types and instances, and the request-use-release discipline.
Mutual exclusion, hold and wait, no preemption, and circular wait - necessary but not sufficient.
Processes, resources, request and assignment edges, and the cycle rule.
Prevention and avoidance, detection and recovery, and ignoring the problem.
Breaking one of the four conditions: sharing, all-or-nothing requests, preemption, and resource ordering.
A priori maximum demands and the safe-state guarantee.
Dashed claim edges, edge conversions, and the no-cycle granting rule.
Available, Max, Allocation, and Need; the safety and resource-request algorithms.
The wait-for graph for single instances and the detection algorithm for multiple instances.
Process termination and resource preemption, with priority-first victim selection.
What to expect on the exam and the computations to practice.
How real systems handle deadlocks, from databases to network service classes.
Exam Revision Notes
Below is the distilled, exam-ready core. Every entry comes from the full explanation above. Use this section for rapid review; return to the main notes when a point needs more context.
The Deadlock Problem and the Resource Model
Must-know: A deadlock is a set of processes each holding a resource and waiting for a resource held by another process in the set; the mutual-waiting cycle is its signature.
Top pitfall: Confusing resource types with instances, or treating ordinary waiting as a deadlock.
Self-check: Why do two processes and two resources suffice to form a deadlock?
Connects to: 10.2
The Four Necessary Conditions for Deadlock
Must-know: The four necessary conditions — mutual exclusion, hold and wait, no preemption, circular wait — must hold at the same time; they are necessary, not sufficient, and they are the characterizations, not the tools.
Top pitfall: Writing the characterizations when asked for the tools, or the tools when asked for the characterizations.
Self-check: Why is breaking any single condition enough to prevent a deadlock, even though all four are required?
Connects to: 10.1, 10.3
Resource Allocation Graphs
Must-know: Cycle rules: no cycle means no deadlock; with a single instance per type a cycle is necessary and sufficient for deadlock; with several instances a cycle is necessary but not sufficient.
Top pitfall: Declaring a deadlock from any cycle without checking instance counts, or misreading request edges as allocations.
Self-check: In a graph with a cycle, what decides whether the cycle is a deadlock?
Connects to: 10.2, 10.7
The Three Strategies for Handling Deadlocks
Must-know: Three strategies: prevention/avoidance (never enter the deadlock state), detection and recovery (enter, detect, recover), and ignore (act as if it never happened) — the one real systems lean on.
Top pitfall: Forgetting that prevention and avoidance prevent entry into the deadlock state, while detection and recovery accept that it can happen.
Self-check: Why do real-time systems often ignore deadlocks?
Connects to: 10.5, 10.6
Deadlock Prevention
Must-know: Prevention needs no a priori information and knocks out one condition: shareability kills mutual exclusion, all-or-nothing or release-first protocols kill hold and wait, forced preemption kills no preemption, and increasing-order enumeration kills circular wait. Cost: lower utilization and throughput.
F(R_i) < F(R_{i+1}) for every step of a wait chain, making F(R_0) < F(R_0) impossible
Top pitfall: Believing the ordering rule is self-enforcing: applications must follow it, and preemption only works for saveable resources.
Self-check: Why can the disk-drive/file/printer ordering never allow a circular wait?
Connects to: 10.2, 10.6
Deadlock Avoidance: Safe and Unsafe States
Must-know: Avoidance needs a priori maximum-demand declarations. Safe state = a completion sequence exists (no deadlock); unsafe state = possibility of deadlock only; all deadlocks are unsafe, not all unsafe states are deadlocks.
Top pitfall: Treating an unsafe state as a deadlock: it is only a possibility, and a helpful release order can still rescue it.
Self-check: In the twelve-tape-drive example, why does granting P2 one more drive turn a safe state unsafe?
Connects to: 10.5, 10.7, 10.8
Avoidance with Single-Instance Resources: Claim Edges
Must-know: Claim edges (dashed) record future requests; conversion claim -> request -> assignment -> claim. Grant only if converting the claim edge to a request edge creates no cycle; the method works only for single-instance types.
Top pitfall: Granting a request just because the resource is available, without the cycle test; using the graph method when a type has several instances.
Self-check: Why may a request for a free resource be refused in the claim-edge method?
Connects to: 10.3, 10.6, 10.8
The Banker's Algorithm
Must-know: Compute Need = Max − Allocation; run the safety algorithm (Work starts as Available, each finished process adds its allocation back); for a request, check Request <= Need and Request <= Available, pretend to allocate, and grant only if the pretend state is safe, else restore.
Top pitfall: Granting a request after the two checks alone, without running the safety algorithm on the pretend state; forgetting to restore the old state on refusal.
Self-check: Why is P4's request (3,3,0) refused even though it passes both checks?
Connects to: 10.6, 10.7
Deadlock Detection
Must-know: Single instance: wait-for graph with process nodes only; a cycle means deadlock, detection costs O(n^2). Multiple instances: detection algorithm — Work = Available; Finish false when Allocation != 0; any process left with Finish false is deadlocked.
O(n^2) cycle detection cost for n processes
Top pitfall: Using the wait-for graph for multi-instance systems, or forgetting that a process whose request cannot be satisfied keeps Finish false and is therefore deadlocked.
Self-check: In the detection example with Available = (0,0,0), why are P1 through P4 deadlocked but not P0?
Connects to: 10.3, 10.8, 10.10
Deadlock Recovery
Must-know: Two recovery families: process termination (abort all, or one at a time with cycle checks) and resource preemption (victim, rollback, starvation). Abort criteria order: priority first (lowest integer is highest priority), then execution time, resources used and needed, processes terminated, interactive versus batch last.
Top pitfall: Picking a high-priority process as the first victim; forgetting that starvation cannot be guaranteed against.
Self-check: Which process is aborted first when P3 has high priority but takes 12 minutes?
Connects to: 10.9, 10.4
Exam Guidance Summary
Must-know: Practice the full Banker's computation pipeline, memorize the four conditions and cycle rules, and know which algorithm applies to which instance count.
Top pitfall: Writing characterizations instead of tools in the exam.
Self-check: Which avoidance algorithm applies to single-instance systems, and which to multi-instance systems?
Connects to: 10.2, 10.3, 10.8, 10.9
Key Industry Applications
Must-know: The ignore strategy is what practice leans on because deadlocks are rare; the elaborate machinery is insurance for mission-critical cases.
Top pitfall: Assuming production systems run the Banker's algorithm — most do not.
Self-check: Why does the database world never skip detection and recovery?
Connects to: 10.4, 10.10
Was this lecture useful?
BitsNotes AI Assistant
Subject Notes AssistantConfigure AI Chat
Choose how to access the chatbotSigned in as
Powered by BitsNotes — 20 messages per day. No API key needed. Want unlimited access? Use "Bring Your Own Key" mode.
Sign in to use AI Chat
Get 20 free AI messages per day to ask questions about your lecture notes. Sign in with Google or GitHub — it takes 5 seconds.
Sign In to BitsNotesSwitch to "Bring Your Own Key" tab above for unlimited access with any OpenAI-compatible provider.