CSE 513
CSE 513 · WEEK 04 · READING

Deadlocks and resource allocation

When can processes wait forever, and what can we do about it?

Select figures for full-size views. Predict each graph and allocation decision before opening its worked answer.

1. Exclusion and progress are separate obligations

Week 3 explained how to keep concurrent operations from corrupting shared state. A mutex gives one owner access to a critical section; a condition variable lets a thread wait for a predicate protected by a mutex. These mechanisms do not, by themselves, guarantee that a program will ever finish. A system can enforce every ownership rule and still reach a state in which the release needed by each waiting participant depends on another blocked participant. Week 4 studies that failure: deadlock.

Start with two workers and two ordinary, nonrecursive mutexes, L1 and L2. Worker A acquires L1 and then L2. Worker B acquires L2 and then L1. Each performs its update only after acquiring both, and releases the locks after the update. There are executions that complete: A might obtain both and finish before B begins. Correctness, however, must cover every execution permitted by the protocol. The following interleaving is also permitted.

A permitted interleaving of opposite lock orders
StepThread AThread B
1Acquires L1
2Acquires L2
3Waits for L2
4Waits for L1

After step 4, A cannot reach its unlock because it needs L2, held by B. B cannot reach its unlock because it needs L1, held by A. Giving them more processor time does not make the missing resources appear. Under the stated protocol there is no internal transition that releases either lock. External termination or recovery could change the state, but that possibility does not make the existing protocol free of deadlock.

Predict: would moving these workers onto different CPU cores fix the problem?

No. The dependency is on lock ownership, not available processor time. Two cores can allow the conflicting first acquisitions to occur simultaneously; they do not create a release path once both second acquisitions block.

A requests L2 held by B, while B requests L1 held by A.
The two-lock example forms a closed ownership/request cycle. AI-generated instructional illustration. Select for the full-size image.

How to study this chapter

Read Sections 1–8 with a pencil beside the figures. For every arrow, identify the participant that is waiting and the participant that could release the resource. This first pass connects the classroom examples to resource graphs and prevention. In Sections 9–15, keep a separate sheet for the allocation tables. Write the current state before trying a grant, and distinguish the real state from the scratch state used to test it. Sections 16–17 provide optional practice and questions for checking your explanation.

By the end, you should be able to show why a particular set cannot progress, give a counterexample to the claim that every graph cycle means deadlock, and justify an allocation decision with a complete trace. Naming the four conditions is useful, but applying them to the exact resources in a scenario is the stronger skill. The two C programs help compare predictions with observations after you have worked through the model.

2. State the resource model before diagnosing a wait

A resource type groups interchangeable instances. One mutex is a type with one instance. A pool of three equivalent connections is a type with three instances, if any connection can satisfy the same modeled request. Two devices that serve different requirements should not be merged into one type merely because both are devices. The request, use, release lifecycle makes the accounting explicit: a participant requests instances, uses granted instances and eventually returns them.

The basic textbook model assumes reusable resources, known allocations and blocking requests that require the specified resources. It reasons about a consistent snapshot, and about completion that releases held resources. It does not automatically describe timeouts, cancellation, hardware failure, consumable messages, alternative “X or Y” requests, or hidden dependencies in a remote service. Those situations require an expanded model. A convincing diagnosis names which participants are blocked, which resources they retain, what they are waiting for, and who could perform the necessary release.

A slow or unresponsive program is therefore a starting observation, not a proof of deadlock. A worker may still be computing, blocked on a slow disk, starved by an admission policy or waiting for an external event. Deadlock is stronger: the required enabling events depend on participants that cannot make progress within the relevant dependency set. Preserve this distinction when interpreting experiments and operational incidents.

A bridge as a resource-allocation model

On the narrow bridge, each occupied road section represents a resource. Cars approaching from opposite directions can hold their current sections while waiting for the section ahead. If neither can pass, recovery may require one or several cars to back out, releasing sections and rolling back progress. A rule admitting one direction at a time prevents the conflict, but fair turn-taking is also needed so that one side does not wait indefinitely.

The original narrow-bridge diagram with vehicles occupying sections on both approaches and inside the bottleneck.
Original bridge-crossing figure, Silberschatz, Galvin and Gagne, Operating System Concepts, 2002, slide 8.3. The road-section model connects resource ownership, rollback and starvation. Select for the full-size image.
Bridge exercise: does placing a traffic light at each entrance automatically solve the problem?

Only if the lights implement a coordinated admission rule. Two independent green lights can admit opposing traffic into the same bottleneck. A suitable controller permits one direction, waits for admitted traffic to clear, and then permits the other. To discuss starvation, also specify when a waiting direction receives its turn. Mutual exclusion between directions and fairness between their queues are separate requirements.

Model check: is the entire bridge one resource, or is each section a resource?

Both are possible models, but they describe different protocols. Treating the whole bridge as one exclusive resource means a controller reserves it before entry. Treating sections separately allows a car to hold one section while requesting the next, which exposes the hold-and-wait problem in the figure. Do not switch between these models halfway through the argument.

3. The four necessary conditions

Mutual exclusion means that at least one relevant resource cannot be used simultaneously by competing participants. A mutex enforces this directly. Read-only data can often be shared, but an update to a shared invariant may still require exclusion. Removing protection without preserving the invariant replaces a waiting problem with a correctness problem.

Hold and wait occurs when a participant retains an allocated resource while requesting another. In the two-lock example, each worker keeps its first mutex throughout the wait for the second. The same pattern can span layers: a thread can hold an application lock while waiting for a connection whose current user needs that application lock to complete.

No preemption means that the resource cannot simply be reclaimed from the current holder. Do not confuse this with CPU scheduling. The operating system may preempt a thread and run another thread while the first still owns its mutex. Taking that mutex away could expose a partially updated object; ownership removal does not repair the object’s state.

Circular wait is a closed chain of dependency: each participant waits for a resource held by the next, and the last waits for one held by the first. All four conditions are necessary for deadlock in the reusable-resource model. A design that permits these conditions has a vulnerability; it does not follow that every run reaches a deadlocked state. Prevention removes at least one condition from the allowed protocol.

Explain the distinction: can a preemptive operating system still have nonpreemptible resources?

Yes. Processor preemption suspends execution; it normally leaves lock ownership unchanged. A resource is preemptible only if the system can reclaim it while preserving or restoring the correctness of its state.

Gas-station bathroom key attached to a long stick.
The instructor’s bathroom-key analogy illustrates a single reusable resource with exclusive access. AI-generated instructional illustration. Select for the full-size image.

Circular wait in a street grid

Cars and trucks block four intersections around a city block, forming a closed chain of occupied road space.
Original Traffic Deadlock for Exercise 8.4, Silberschatz, Galvin and Gagne, Operating System Concepts, 2002, slide 8.41. Select for the full-size image.

Trace a vehicle’s wait for the space ahead, then continue through the vehicles occupying that space until the chain returns to its starting point. Under the model of exclusive road sections and forward movement without forced retreat, vehicles hold resources while waiting around a cycle. Keeping the exit clear before entering an intersection prevents this form of gridlock. Once the cycle exists, recovery may require coordinated backing up rather than simply asking every driver to try again.

Street-grid exercise: locate all four conditions in the picture.

A road section cannot hold two vehicles at once, giving mutual exclusion. Each vehicle occupies its current space while waiting for space ahead, giving hold-and-wait. Under the assumed forward-only protocol, a controller cannot simply remove a vehicle from its occupied space, giving no preemption. Following the blocked exits around the block closes a circular wait. More green lights do not create free exit space. A prevention rule must constrain entry before the blocking arrangement forms.

The bathroom-key analogy isolates a smaller part of the reasoning. Picking up the only key represents acquiring one exclusive resource, and returning it represents release. A queue for that single key is not automatically a deadlock: the current user may finish and return it. To form a deadlock, add a dependency that prevents that return, such as the holder waiting for another resource owned by someone who is waiting for the key. The same illustration can therefore explain mutual exclusion without claiming that every queue is a deadlock.

4. Deadlock, starvation and livelock

In deadlock, the relevant set cannot progress because its dependencies are closed. In starvation, an individual participant may wait indefinitely even while other participants complete useful work. A lock or reservation policy that always favors new short requests can repeatedly bypass a large request. Removing circular wait does not itself impose fairness or a bound on bypassing.

In livelock, participants continue changing state without completing useful work. Imagine two workers that each obtain a different first lock, fail to obtain the second, release, and retry at exactly the same time forever. The permanent blocking cycle has been removed, but the retry pattern makes no progress. Backoff, asymmetry, bounded retries or a coordinator can change the behavior. Randomized backoff can reduce repeated collisions; a single successful experiment does not establish a deterministic bound on waiting.

A timeout can detect that a chosen deadline was missed. It cannot, by itself, distinguish these three conditions from slow legitimate work. Ask what state can still change, whether useful operations are completing, and which assumptions would allow the waiting participant to proceed. This vocabulary prevents the misleading conclusion that every hang is deadlock and every retry is recovery.

Classify three observations: a closed lock cycle, an endlessly bypassed request, and synchronized release-and-retry.

The closed lock cycle is deadlock under the stated release rules. The endlessly bypassed request is starvation if other work continues to finish. Synchronized release-and-retry is livelock if state keeps changing but useful work never completes. A trace with no output does not reveal which of these is present. Establish ownership, requests and completed work before assigning the label.

5. Resource-allocation graphs and the role of instances

A resource-allocation graph contains thread vertices and resource-type vertices. In the source figures, threads are circles, resource types are rectangles, and dots identify individual instances. A request edge points from a thread to a resource type. An assignment edge points from an instance to its owning thread. Reversing either interpretation reverses the dependency argument, so read the arrows aloud before drawing conclusions.

T1 requests R1 held by T2; T2 requests R3 held by T3; T3 has no outgoing request.
T3 can complete and release the resource that starts a chain of progress. Original figure retained from Silberschatz, Galvin and Gagne, Operating System Concepts, 10th edition, Chapter 8, slide 8. Select for the full-size image.

Here R1 and R3 each have one instance, R2 has two, and R4 has three. T1 holds an R2 instance and requests R1. T2 holds R1 and the other R2 instance, and requests R3. T3 holds R3 but has no outgoing request. If T3 completes, it releases R3 to T2; completion of T2 then makes the resources needed by T1 available. The unused R4 instances do not participate in this chain.

The preceding graph now also has T3 requesting R2, whose two instances belong to T1 and T2.
One additional request closes the dependency set. Original figure retained from Silberschatz, Galvin and Gagne, Operating System Concepts, 10th edition, Chapter 8, slide 9. Select for the full-size image.

Now T3 requests R2. There is no free R2 instance: both are held by participants that cannot finish. T1 needs T2’s R1; T2 needs T3’s R3; T3 needs an R2 release from T1 or T2. Under the model these three threads are deadlocked. The existence of three free R4 instances does not help, because resource types are not interchangeable with one another.

A cycle connects T1 and T3 through R1 and R2, while T2 and T4 hold alternative instances and have no requests.
A cycle can coexist with a completion outside the cycle. Original figure retained from Silberschatz, Galvin and Gagne, Operating System Concepts, 10th edition, Chapter 8, slide 10. Select for the full-size image.

The third graph contains a cycle through T1, R1, T3 and R2, but each type has two instances. T2 or T4 can finish without waiting on the cycle. Its released instance can satisfy a waiting request and allow the cycle to dissolve. Therefore, with multiple instances, finding a cycle is not a sufficient diagnosis. Counts and potential completions matter.

For current requests on reusable resources, no cycle means no resource deadlock. If every relevant resource type has a single instance, a cycle is sufficient as well as necessary. With several instances, a cycle indicates the possibility of deadlock, and a reduction or matrix-based analysis is needed. Later we introduce dashed future-claim edges: those require a different interpretation because a possible future request is not a blocked request now.

Work it out: A holds X and requests Y; B holds Y and requests X. How can adding a second X instance change the result?

If C holds the second X instance and can finish without further requests, C can release it. B can obtain X, finish and release Y; A can then finish. This conclusion requires C to have a completion path. Merely increasing a count on paper is not enough if every holder is also blocked.

6. Wait-for graphs and dining philosophers

A single-instance resource graph on the left is converted into a thread-only wait-for graph on the right.
Remove each resource vertex by connecting the requesting thread to its owner. Original figure retained from Silberschatz, Galvin and Gagne, Operating System Concepts, 10th edition, Chapter 8, slide 34. Select for the full-size image.

For a single-instance resource, replace Ti → R → Tj with Ti → Tj: Ti waits for Tj to release the required resource. In the retained example, T1 → T2 → T4 → T1 is a cycle. There is another route through T3, so cycles can overlap. T5 has no outgoing wait, but completing T5 does not by itself release all the resources needed by the blocked cycle. It is not enough to find some thread that can run; the released resources must resolve the relevant waits.

A graph algorithm using adjacency lists can find a directed cycle in O(V + E) time. A dense matrix representation can require quadratic work in the number of vertices. State the representation when quoting a complexity bound. The more important modeling limitation is that a graph can only reveal dependencies it contains: omitted locks, condition predicates or external waits can invalidate an otherwise neat picture.

Five philosophers surround a table with shared utensils.
Revisit the Week 3 analogy as an ownership and request graph. AI-generated illustration retained from the instructor’s Week 3 dining-philosophers analogy. Select for the full-size image.

When all five philosophers hold one utensil and wait for their other neighbor’s utensil, each utensil has one owner and each waiter depends on the next. Number the utensils 0–4 and require acquisition of the lower-numbered utensil before the higher-numbered one. The philosopher between utensils 4 and 0 must therefore take 0 first, breaking the uniform circular acquisition pattern. This establishes deadlock prevention for those acquisitions, not a fairness guarantee that every philosopher eats within a fixed time.

Five places at a round table share five chopsticks.
Textbook dining-philosophers model. Each participant needs both adjacent chopsticks. Silberschatz, Galvin and Gagne, Operating System Concepts, 10th edition, Chapter 7, slide 12. Select for the full-size image.

7. Prevention changes the permitted protocol

Compare the information and costs of the four approaches
ApproachDecisionMain cost
PreventionForbid a necessary conditionRestrictions on access
AvoidanceGrant only if state stays safeMaximum-demand information
Detection + recoveryAllow, detect, then repairMonitoring and lost work
Accept the riskNo general mechanism hereApplication must handle failures

Prevention removes one necessary condition. Sharing immutable data, or introducing an appropriate spooler, can change the resource model so that an exclusive physical device is no longer directly held by every producer. Such changes must preserve the operation’s meaning; removing a lock around a mutable balance is not a prevention method if concurrent updates can corrupt it.

To remove hold-and-wait, reserve the complete required set before work begins, or release current allocations before requesting a new set. Whole-set reservation must itself be coordinated. Several independent “is it free?” tests followed by partial acquisition can reproduce the very problem the design was intended to avoid. Reserving a worst-case set for an entire job can also leave resources idle, and large reservations can starve when smaller requests repeatedly consume available capacity.

Resource preemption needs a valid state-recovery mechanism. Saving and restoring a computational state may be possible; undoing a printed page or a message already acted upon is different. A protocol that releases resources and retries must specify what happens to partial work, how duplicate external effects are prevented, and whether the same participant can lose on every retry. A progress strategy has to cover state and policy, not just the unlock instruction.

Why checking availability is not a reservation

Suppose a job requires both a scanner and a printer. Two jobs each inspect an availability list and see both devices free. If one then acquires the scanner while the other acquires the printer, both may block on their second acquisition. The earlier observations did not reserve anything. A whole-set allocator must make the decision and record both allocations as one coordinated operation. If the complete set is unavailable, the job waits without retaining part of that set.

Compare policies: what does whole-set reservation sacrifice?

A job may reserve the printer long before it needs to print, preventing another job from using an otherwise idle device. A large request can also be repeatedly bypassed by smaller ones. The protocol removes hold-and-wait among the managed resources, but utilization and fairness still require attention. Any locks held while calling the allocator must also be included in the dependency analysis.

8. Global lock order and its proof

Assign every lock a unique rank and require acquisitions to follow increasing rank. Suppose a circular wait existed. Following its acquisition dependencies would give rank(L1) < rank(L2) < … < rank(Lk) < rank(L1), a contradiction. This is a compact proof that the allowed acquisition rule excludes circular wait. Releasing in reverse order is a clear coding convention, but the contradiction relies on the acquisition order.

Two textbook pthread workers acquire first_mutex and second_mutex in opposite orders.
The second worker violates the common ordering rule. The screenshot is retained unchanged for analysis. Original figure retained from Silberschatz, Galvin and Gagne, Operating System Concepts, 10th edition, Chapter 8, slide 15. Select for the full-size image.

In the original code, one worker acquires first_mutex then second_mutex, while the other does the reverse. Make both follow the same ranking. In real code, check the return values and audit every route to acquisition: callbacks, error paths and helper functions can introduce hidden inversions. The image is a teaching fragment, not the checked practice program supplied below.

For dynamically selected resources, use a stable ordering key. A transfer between two accounts should acquire the lock associated with the smaller account ID first, regardless of transfer direction. Handle identical source and destination explicitly; acquiring the same nonrecursive lock twice is a separate self-deadlock problem. The rule must be global across the participating code, rather than a local preference that another component reverses.

A nonblocking try-lock can avoid waiting while retaining the first lock, but only if failure is followed by the specified release. The POSIX mutex specification defines EBUSY for an unavailable try-lock. An immediate retry loop may still livelock or starve. Error-checking mutex behavior should not be mistaken for a general detector of all multi-thread dependency cycles.

Find the flaw: both workers follow L1 then L2, but after obtaining L1 they wait at a two-worker rendezvous.

Only one worker can obtain L1. It waits at the rendezvous for the other, while the other waits for L1. The rendezvous adds a dependency outside the simple two-lock acquisition proof. In the provided ordered mode, there is no such rendezvous while waiting for the first lock.

Both workers acquire L1 of rank 1 before L2 of rank 2.
These are acquisition plans, not simultaneous ownership snapshots. AI-generated instructional illustration. Select for the full-size image.

9. Safety is a guarantee about possible future demand

Avoidance accepts requests only when the resulting state remains safe. A safe state has a completion sequence covering every participant such that each participant’s maximum remaining need can be satisfied by currently available resources plus those released by earlier completions. This is a witness that completion is possible under the model’s assumptions. It is not an instruction to the CPU scheduler, and it does not promise a particular real-time finishing order.

The deadlocked region is contained in the unsafe region, separate from safe states.
Unsafe means that a completion guarantee has been lost; it does not mean every future behavior is already blocked. Original figure retained from Silberschatz, Galvin and Gagne, Operating System Concepts, 10th edition, Chapter 8, slide 19. Select for the full-size image.

Consider 10 units of one resource. A holds 4 and declares a maximum of 6; B holds 3 and declares a maximum of 8. Three units are available. A’s remaining need of 2 can be met; when A finishes, 7 units become available, enough for B’s remaining need of 5. Thus A, B witnesses safety.

Now tentatively grant B another two units. Only one is available; A may need two more and B may need three more. Neither is guaranteed to finish using that one unit. This tentative state is unsafe. It need not be deadlocked now: B might complete without using its entire declared maximum. Avoidance nevertheless refuses the grant because its rule is to preserve a guarantee against the allowed maximum demands, not to guess that demands will turn out smaller.

Predict: if no safe sequence exists, must every participant already be blocked?

No. Safety considers possible future maximum demand. An unsafe state may still contain runnable participants or participants that will finish using less than their declared maximum. Detection uses current outstanding requests instead.

Three free units, four held by A and three held by B.
With three units free, A can receive its remaining two and finish. Its release leaves seven available. AI-generated instructional illustration. Select for the full-size image.
One free unit, four held by A and five held by B.
After granting B two more units, neither maximum remaining need fits the free pool. The state is unsafe even if no one is currently blocked. AI-generated instructional illustration. Select for the full-size image.
Token exercise: from the original safe pool, can B receive one more unit instead of two?

Yes. The state becomes Available = 2, A Allocation = 4 and B Allocation = 4. The remaining needs are 2 for A and 4 for B. A can borrow the two free units, complete and return six, leaving six available for B. The sequence A, B remains a witness. The decision depends on the resulting counts, not simply on whether B already owns several units.

10. Avoidance graphs include future claims

R1 is assigned to T1; T2 requests R1; both threads have dashed possible future claims on R2.
Dashed edges represent declared possibilities, not current blocking requests. Original figure retained from Silberschatz, Galvin and Gagne, Operating System Concepts, 10th edition, Chapter 8, slide 22. Select for the full-size image.

For a single-instance avoidance model, a claim becomes a request when the participant actually asks for the resource, becomes an assignment when granted, and returns to a claim on release. Advance claims let the allocator consider dependencies that are not yet active. The source slide’s claim notation is interpreted here as Ti may request Rj; the thread index is i.

R2 is tentatively assigned to T2, closing a cycle with T1’s dashed future claim on R2.
The new assignment is unsafe because a future request can complete the blocking cycle. Original figure retained from Silberschatz, Galvin and Gagne, Operating System Concepts, 10th edition, Chapter 8, slide 23. Select for the full-size image.

T2 is waiting for R1, held by T1. If R2 is now assigned to T2, a later request by T1 for R2 would leave each waiting for the other. The avoidance rule rejects an assignment that creates this cycle involving claims. Do not call T1 currently blocked merely because its dashed claim participates in the unsafe-state picture. For multiple interchangeable instances, Banker’s algorithm replaces this simple graph rule with vector accounting.

11. Banker’s algorithm: account for every instance

Banker’s algorithm requires Available, Allocation and a declared Max for every thread and resource type. Need = Max − Allocation is the maximum additional demand. A request must not exceed Need, and all quantities must remain nonnegative. For each resource type, total instances equal available instances plus the allocations summed across all threads. An allocator must serialize changes to this state; concurrently checking the same old snapshot and then committing two grants would undermine the safety argument.

Original textbook snapshot; Available = (3, 3, 2), totals = (10, 5, 7)
ThreadAllocation A B CMax A B C
T00 1 07 5 3
T12 0 03 2 2
T23 0 29 0 2
T32 1 12 2 2
T40 0 24 3 3
Compute the remaining Need component by component
ThreadMaxAllocationNeed
T07 5 30 1 07 4 3
T13 2 22 0 01 2 2
T29 0 23 0 26 0 0
T32 2 22 1 10 1 1
T44 3 30 0 24 3 1

The safety test works on scratch data. Set Work = Available and mark every thread unfinished. Find a thread whose entire Need vector fits in Work. Pretend it receives what remains, completes and returns everything; the net update is Work = Work + Allocation[i]. Mark it finished and repeat. If all threads can finish, the resulting sequence is a witness. If unfinished threads remain and none qualifies, the state is unsafe.

Work = Available
Finish[i] = false for every i
while an unfinished i has Need[i] <= Work:
    Work += Allocation[i]
    Finish[i] = true
safe iff every Finish[i] is true

The vector comparison is componentwise. A surplus of A does not compensate for missing C. Returning Allocation rather than Max is also essential: the additional Need was only borrowed from Work during the hypothetical completion. Work − Need + Max simplifies to Work + Allocation. Adding Max directly would count the newly lent resources twice.

A small calculation before the full trace

Consider two resource types X and Y with totals (3, 2). P holds (1, 0) and declares Max = (2, 1). Q holds (1, 1) and declares Max = (2, 2). Available is therefore (1, 1), and each participant’s remaining Need is (1, 1). Either participant can be the first in a safe sequence. If P completes first, Work becomes (2, 1), which can cover Q’s remaining need.

Calculate: in that small example, may Q receive one additional X?

Yes. Tentative Available becomes (0, 1), Q Allocation becomes (2, 1), and Q Need becomes (0, 1). Q can finish first and return its allocation, leaving Work = (2, 2). P then fits. The old witness P, Q no longer starts successfully, but the new witness Q, P does. A tentative grant can change the valid order without making the state unsafe.

12. Work through a complete safety trace

One valid witness: T1, T3, T4, T2, T0
StepThread and remaining needWork after completion
Start—3 3 2
1T1: Need 1 2 25 3 2
2T3: Need 0 1 17 4 3
3T4: Need 4 3 17 4 5
4T2: Need 6 0 010 4 7
5T0: Need 7 4 310 5 7

At Work = (3, 3, 2), T1 needs (1, 2, 2), so it can finish. Its allocation (2, 0, 0) increases Work to (5, 3, 2). T3 then needs only (0, 1, 1), and completion returns (2, 1, 1), giving (7, 4, 3). T4 fits next and returns two C units. With (7, 4, 5), T2’s remaining need of six A units can be met. T0 then completes, and the final Work equals the full resource totals.

Other witnesses can be correct. A scanner that chooses a different eligible thread may produce T1, T3, T0, T2, T4 instead. Validate each local comparison and update rather than matching the printed sequence character for character. Once an eligible thread completes, Work never decreases in this model; its returned allocation can only make later completions easier. An independent enumeration of all thread orders can check a small example, even though enumeration is not an efficient general implementation.

Calculate: why is Work after T1 equal to (5, 3, 2), rather than (6, 5, 4)?

T1 currently owns (2, 0, 0), so Work gains only that existing allocation. Its remaining (1, 2, 2) would first be borrowed and then returned. Adding its Max (3, 2, 2) directly incorrectly creates additional instances.

13. Grant, wait, or restore the old state

For a new request, first test Request ≤ Need. An excess is a claim violation. Next test Request ≤ Available. If it fails, there are not enough free instances and the requester must wait. Otherwise make a tentative state: reduce Available by the request, increase that thread’s Allocation and reduce its Need. Run the safety test on this tentative state. Commit only when safe; a refusal must leave the original allocation unchanged.

Starting from the original five-thread snapshot, T1 requests (1, 0, 2). Its old Need is (1, 2, 2), and Available is (3, 3, 2), so both checks pass. Tentative Available becomes (2, 3, 0), T1 Allocation becomes (3, 0, 2), and its remaining Need becomes (0, 2, 0). All other rows remain unchanged.

A safe completion trace after granting T1 (1, 0, 2)
StepCompletionWork
Start—2 3 0
1T15 3 2
2T37 4 3
3T47 4 5
4T07 5 5
5T210 5 7

Now continue from that granted state. T4 asks for (3, 3, 0). Its claim allows the request, but only two A units are available, so the availability test refuses it without simulation. Keep the state unchanged. From that same state, independently consider T0 requesting (0, 2, 0). This request is both within its remaining claim and currently available, but a tentative grant leaves Work = (2, 1, 0). T1 needs two B; T3 needs a C; and T0, T2 and T4 cannot meet their A or other needs. No completion can begin. The grant is unsafe and must be rolled back.

Predict: does refusing T0 prove that the state before its request was deadlocked?

No. The pre-request state has the safe sequence shown in the table. The unsafe result belongs to the hypothetical grant. Restoring the previous state preserves the earlier safety guarantee.

Always label the snapshot used by a request exercise. “From the original state” and “after granting T1” are different inputs. Do not apply a refused request when moving to the next problem. The supplied checker tests both refusal and preservation of the old state, because a correct classification is not enough if the allocator has already mutated its live data.

14. Detection uses current requests, not maximum claims

Detection allows resource allocation to enter a deadlocked state and then identifies a set requiring intervention. For multiple instances, its Request matrix records currently outstanding requests, not possible remaining maximum demands. Set Work = Available. Initially mark zero-allocation participants finished for the purpose of identifying resource holders retaining the deadlock. Repeatedly find an unfinished holder whose current Request fits, model its completion, and add its Allocation to Work.

This reduction assumes that once a modeled current request can be met, the participant can eventually complete and return its allocation. A real program that immediately makes additional unmodeled requests needs a new snapshot or a richer model. A participant holding no resources can still remain blocked behind the deadlocked holders; distinguish membership in the resource-retaining set from every task affected by that set.

Detection example: totals (7, 2, 6), Available (0, 0, 0)
ThreadAllocation A B CRequest A B C
T00 1 00 0 0
T12 0 02 0 2
T23 0 30 0 0
T32 1 11 0 0
T40 0 20 0 2
A completion reduction despite zero initial availability
CompletionWork beforeWork after
T00 0 00 1 0
T20 1 03 1 3
T33 1 35 2 4
T15 2 47 2 4
T47 2 47 2 6

T0 and T2 have zero current requests. Modeling their completion releases enough resources for the remaining holders. Zero availability alone therefore does not establish deadlock. Now change only T2’s current Request from (0, 0, 0) to (0, 0, 1), leaving the allocation and available vectors fixed. T0 can complete, giving Work = (0, 1, 0), but no remaining holder fits. T1, T2, T3 and T4 retain the deadlock under the model.

Distinguish the matrices: why not use Banker’s Need instead of Request in this detector?

Need represents a possible future maximum. Substituting it asks whether the state has a safety guarantee, and can classify an unsafe but currently progressing state as though its maximum demands were already blocked requests. Detection and avoidance answer different questions.

A waiter can be affected without retaining the cycle

Suppose P owns the only X instance and waits for Y, while Q owns the only Y instance and waits for X. A third participant Z holds nothing and waits for X. P and Q retain the deadlock. Z is blocked behind them, but terminating Z would release no resource and would not break their cycle. This explains why the textbook matrix detector can mark zero-allocation participants finished initially while they remain affected in the real system. Its result identifies the resource-holding set that prevents reduction.

Detection exercise: would adding one free X instance break this particular deadlock?

Yes, if the new instance is interchangeable with X and Q can complete after receiving it. Q can then release Y, allowing P to finish and release its X. Z can eventually receive a released instance under a suitable admission policy. The conclusion depends on the completion assumption and resource equivalence, not merely on increasing an unrelated resource count.

15. Detection and recovery form a complete policy

A detector might run on a blocked request, periodically, or after a delay. Frequent checks cost more but can reduce the amount of work accumulated before recovery. The straightforward repeated-scan matrix algorithm uses O(m n²) operations. Operationally, a consistent snapshot and complete dependency coverage can matter more than that asymptotic cost. An inconsistent snapshot may combine an old owner with a new request, and an omitted callback or remote dependency may conceal the real cycle.

Recovery can abort all deadlocked work, or choose a victim and check again after the intervention. Cycles can overlap, so the rule “one victim per cycle” is not a complete recovery algorithm. Recompute the dependencies. Consider the participant’s priority, completed work, restart cost, held resources, user impact and history of previous rollbacks. A rule that always chooses the same cheapest task can turn recovery into starvation.

Resource preemption and rollback require a state boundary that supports them. Stopping an arbitrary thread in a shared process can leave invariants broken and ownership unresolved. An application-defined task or transaction can provide a safer recovery unit, but it must account for effects already observed outside the rollback boundary. Retrying a request after a lost response can duplicate an operation that actually succeeded. Ordinary mutexes do not supply transaction rollback automatically.

Linux lockdep is a useful practical connection: it tracks lock-class dependency patterns in the kernel and can report potential locking inconsistencies without waiting for a particular execution to freeze. It is not an application-wide promise that pthread mutexes diagnose every cycle. Instrumented coverage, configuration and the modeled dependencies limit what any such tool can establish.

An interrupted transfer changes balances 100 and 50 to 90 and 50; rollback restores 100 and 50.
Illustrative transaction rollback repairs the state before retrying. Releasing a lock alone cannot restore these balances. AI-generated instructional illustration. Select for the full-size image.

16. Optional practice: predict, run, explain

Download lock-order.c and banker.c. These are optional study activities, not a released graded assignment. No deadline or submission rule is implied. AI assistance is permitted: use it to suggest adverse interleavings, question your reasoning and help interpret diagnostics, but keep your own predicted state and raw observations.

cc -std=c11 -Wall -Wextra -O2 -pthread lock-order.c -o lock-order
timeout 3s ./lock-order deadlock
echo $?
./lock-order ordered
./lock-order try
cc -std=c11 -Wall -Wextra -O2 banker.c -o banker
./banker

The deadlock mode deliberately synchronizes the workers after each owns a different first lock. Each then asks for the other. Run it only under the shown external timeout; it is intended not to complete by itself. The ordered mode has both workers acquire L1 then L2, and deliberately has no two-worker rendezvous while one waits for L1. The try mode ensures both first locks are held before either try-lock, and both attempts finish before either first lock is released. Each second acquisition therefore reports EBUSY; the mode then releases and exits rather than retrying forever.

On Ubuntu, the GNU timeout utility normally returns 124 when its deadline expires. The exit status alone is not a deadlock proof. Combine it with the source protocol, the two ownership messages and the two second-request messages. A failure to print those messages, a compilation error or a very short deadline is different evidence. Record the operating system and compiler versions, complete commands, output and status before drawing conclusions.

The Banker checker prints a valid safe sequence, grants T1’s request, and checks unavailable, unsafe and above-maximum requests while verifying that refused requests leave the state unchanged. It models accounting only: it does not run the five threads or allocate operating-system resources. To extend it, change one input at a time and preserve total = Available + sum(Allocation). Compare every new result with a hand trace. A deterministic scan may return a different valid witness from the illustrated sequence.

The supplied programs were compiled with strict warnings and exercised on macOS during preparation. The intentionally blocked runs were terminated by a bounded test harness. The Ubuntu commands and GNU timeout behavior are documented here, but execution on the course Ubuntu environment has not yet been measured. This distinction is part of the evidence record, not a claim that the platforms behave identically in every detail.

Reflect: what must remain true if you remove the rendezvous from deadlock mode?

Opposite lock orders still permit deadlock, but the test no longer guarantees that both first locks are held simultaneously. Some runs can finish. Successful runs would show that an adverse interleaving did not occur, not that the protocol has become deadlock-free.

A prediction and observation record

Before running a mode, draw the expected ownership state at the moment of the second acquisition. After running it, compare that drawing with the printed messages. Message order can vary, so compare the dependency facts rather than demanding one exact transcript.

What each experiment can establish
ModePredictionEvidence to keep
deadlockEach worker retains a different first lock and blocks on the other.Both ownership messages, both second requests and bounded termination.
orderedThe shared acquisition order allows both workers to complete.Completion output, exit status and the acquisition rule in the source.
tryBoth second acquisitions fail while the first locks remain held, then cleanup completes.Both EBUSY reports and successful exit.
bankerSafe grants succeed and refused requests preserve the previous state.The input snapshot, a hand trace and the checker’s classifications.

A useful AI-assisted exercise is to ask for a proposed repair, then search for a counterexample before changing the program. If the proposed repair is “sleep before taking the second lock,” explain which execution can still produce opposite first acquisitions. If it is “always retry,” identify what breaks repeated collisions. Keep the proof obligation visible even when an experiment happens to succeed.

17. Review and study guidance

For each scenario, write the resource types and instance counts, the current allocations, the outstanding requests, and the release assumptions. Then select the appropriate question. A current single-instance cycle supports a deadlock diagnosis. A multi-instance state needs counts and reduction. A maximum-demand allocation question needs a safety test. Keeping these questions separate prevents most false conclusions.

Review 1: identify all four conditions in the opposite-lock-order example.

Each mutex is exclusive; each worker holds one while waiting for the other; neither can have ownership safely taken away by the protocol; and the two requests form a circular wait.

Review 2: does a safe-state witness guarantee that every scheduler and admission policy makes progress?

No. It demonstrates that the resource claims admit a completion order under the model. An unfair admission or scheduling policy can still starve a participant. Safety and the policy that actually realizes progress are separate obligations.

Review 3: how does lock ordering differ from Banker’s algorithm?

Lock ordering forbids a circular acquisition pattern without requiring per-thread maximum resource counts. Banker’s algorithm dynamically checks a tentative grant using declared maxima and current allocation. The methods require different information and impose different restrictions.

Review 4: why is killing a waiting thread not automatically recovery?

The chosen thread may not release the resources needed to break every cycle. It may also leave shared state inconsistent, and other holders can remain deadlocked. Recovery must repair state, release appropriate resources, recheck dependencies and define restart behavior.

Prepare an explanation that includes one graph counterexample, one complete safe-state trace, one refused request with unchanged state, and one lock-order proof. Use the retained textbook figures to rehearse arrow directions and resource counts. When an answer says “it depends,” name the exact model assumption that changes the result. Next week applies similarly careful accounting to memory management and address translation.

A final comparison exercise

Imagine a service whose jobs reserve reusable connections from a pool. A job can retain one connection while requesting a second. Describe three designs: reserve the complete set before starting, check each tentative grant against declared maximum demand, or detect blocked dependencies and restart selected jobs. For each design, state the information it requires and the point at which it changes the job’s behavior.

Worked comparison: what distinguishes these three designs?

Complete-set reservation is prevention: a waiting job holds none of the connections it is trying to reserve, removing hold-and-wait within that allocator. A maximum-demand check is avoidance: it can allow partial allocations but preserves a safe state after each grant. Detection with restart permits a deadlock and later intervenes, so it needs current dependency information and a recovery boundary that can undo or safely repeat the job’s effects. Choosing one does not settle fairness, and unmodeled application locks can invalidate an analysis limited to the connection pool.

18. References and further study

This chapter is adapted from Abraham Silberschatz, Peter B. Galvin and Greg Gagne, Operating System Concepts, 10th edition, Chapter 8. All eight embedded figures from the official slide set are retained with credits. The numerical examples preserve the source values and expand their intermediate steps; the prose, exercises and runnable practice files are original course explanations. The Chapter 7 dining-philosophers diagram is also retained. The bridge-crossing and traffic-grid figures come from the authors’ 2002 Chapter 8 slides (8.3 and 8.41). The bathroom-key and philosopher illustrations carry forward the instructor’s Week 3 analogies; the additional explanatory illustrations are labeled as AI-generated in their captions.

For complementary reading, consult OSTEP’s Common Concurrency Problems. Interface references are linked where used: POSIX mutex semantics, GNU timeout and Linux lockdep. No additional required viewing is assigned for this week.

Search CSE 513