COMP 3150Operating Systems

Module 2 · Unit 7 of 5

Deadlocks

The failure mode of synchronization. A set of processes is deadlocked when each is waiting for a resource held by another, and none can proceed. This unit develops the four necessary conditions, the resource-allocation graph formalism, and the four families of approaches — prevention, avoidance, detection, and recovery.

7 lessons Lab 6 · Deadlock analysis CLO3 ≈ 1.5 weeks at recommended pace
A resource allocation graph with a cycle Processes P1 and P2 are shown as circles, resources R1 and R2 as squares. P1 holds R1 and requests R2; P2 holds R2 and requests R1. The cycle indicates a deadlock. RESOURCE ALLOCATION GRAPH P1 P2 R1 R2 held by requests held by requests cycle → deadlock P1 holds R1, wants R2 · P2 holds R2, wants R1 allocation edge request edge

01 Unit learning outcomes

Upon completion of this unit you will be able to

  • U7.1 — Explain deadlock conditions.
  • U7.2 — Analyse deadlock occurrence.
  • U7.3 — Apply prevention and avoidance techniques.
  • U7.4 — Use detection techniques.
  • U7.5 — Evaluate recovery approaches.

This unit addresses course learning outcomes CLO3 and CLO8. It closes Module 2.

02 Lessons in this unit

The first three lessons develop the theory. The final four examine the four families of practical approaches, from the most conservative (prevention) to the most pragmatic (detection and recovery).

03 What you will learn, lesson by lesson

7.1 Deadlock Fundamentals

  • Define deadlock.
  • Distinguish deadlock from starvation.
  • Distinguish deadlock from livelock.
  • Analyse system-level effects.
  • Recognise deadlock scenarios.

7.2 Deadlock Conditions

  • State mutual exclusion.
  • State hold-and-wait.
  • State no-preemption.
  • State circular wait.
  • Analyse condition necessity.

7.3 Resource Allocation Graphs

  • Construct resource allocation graphs.
  • Identify cycles.
  • Interpret graph structure.
  • Analyse multiple instances.
  • Apply cycle detection.

7.4 Prevention

  • Break mutual exclusion.
  • Break hold-and-wait.
  • Break no-preemption.
  • Break circular wait.
  • Evaluate prevention costs.

7.5 Avoidance

  • Define safe state.
  • Explain resource reservation.
  • Analyse safe sequences.
  • Evaluate avoidance assumptions.
  • Compare avoidance and prevention.

7.6 Banker's Algorithm

  • Describe the safety algorithm.
  • Execute the resource-request check.
  • Trace algorithm state.
  • Analyse complexity.
  • Evaluate practical use.

7.7 Detection and Recovery

  • Apply detection algorithms.
  • Explain termination recovery.
  • Explain preemption recovery.
  • Explain rollback recovery.
  • Evaluate recovery trade-offs.

04 Worked example

A deadlock that appears in real code, and the classic fix.

Two accounts, two locks, one deadlock

A banking system has two accounts, each with its own mutex. Two threads attempt transfers in opposite directions.

void transfer(Account *from, Account *to, int amount) {
    pthread_mutex_lock(&from->lock);
    pthread_mutex_lock(&to->lock);

    from->balance -= amount;
    to->balance   += amount;

    pthread_mutex_unlock(&to->lock);
    pthread_mutex_unlock(&from->lock);
}

/* Thread A */
transfer(&account1, &account2, 100);

/* Thread B (runs concurrently) */
transfer(&account2, &account1, 50);
  1. The interleaving. Thread A acquires account1->lock, then is preempted. Thread B acquires account2->lock. Thread A now waits for account2->lock while holding account1->lock. Thread B waits for account1->lock while holding account2->lock. Circular wait. Deadlock.
  2. Check the four conditions. Mutual exclusion — yes, both locks are exclusive. Hold-and-wait — yes, each thread holds one lock while requesting another. No-preemption — yes, neither thread releases voluntarily. Circular wait — yes.
  3. Fix by breaking circular wait. Impose a global ordering on locks. Lock the lower-numbered account first, regardless of which is "from" and which is "to".
void transfer(Account *a, Account *b, int amount) {
    Account *first  = (a->id < b->id) ? a : b;
    Account *second = (a->id < b->id) ? b : a;

    pthread_mutex_lock(&first->lock);
    pthread_mutex_lock(&second->lock);
    /* ... */
}

With a consistent global ordering, circular wait is impossible. Thread B now also locks account1 first, so the two threads contend on the same lock rather than forming a cycle. One proceeds, the other waits its turn.

The general rule

If your program must hold more than one lock at a time, define a global ordering and always acquire locks in that order. This one discipline eliminates circular wait — and with it, the majority of deadlocks in real systems.

05 Required reading

  • OSTEP — Chapter 32, Concurrency Bugs, and the discussion of deadlock in Chapter 31.
  • Operating System Concepts — Chapter 8, Deadlocks, in full.
  • Dijkstra, E. W. Cooperating Sequential Processes (1965) — the original description of the deadlock problem and the Banker's algorithm.

06 Unit discussion

Discussion prompt

General-purpose operating systems do not use the Banker's algorithm, even though it is provably correct and avoids deadlock entirely. Real systems use prevention (lock ordering) where possible, and detection-and-recovery elsewhere.

Why? Consider the assumptions the Banker's algorithm makes about advance knowledge of resource needs, the complexity of running the safety check on every request, and the actual behaviour of workloads in the field. Argue for or against the use of avoidance in a modern server operating system.

Post your response to the Unit 7 discussion forum and reply to at least two classmates. Supports CLO3 and CLO7.

07 Laboratory connection

Lab 6 — Deadlock Analysis is completed at the end of this unit. It asks you to construct a deadlock deliberately, observe it under gdb, diagnose it using the four conditions, and repair it.

Lab 6 coverage
Lesson Lab 6 exercise
7.1–7.2 DeadlockConstruct a two-thread deadlock and confirm the four conditions
7.3 GraphDraw the resource allocation graph for the lab program
7.4 PreventionFix the program with lock ordering; verify with a stress test
7.6 Banker'sImplement the safety check; feed it a synthetic state table
7.7 DetectionAttach gdb, dump all thread stacks, identify the cycle

08 Knowledge check & quiz

Quick check before you begin

Question 1

State Coffman's four necessary conditions for deadlock. Why must all four hold simultaneously?

Show answer
Mutual exclusion — at least one resource is held in a non-shareable mode. Hold-and-wait — a process holds at least one resource while waiting to acquire others. No-preemption — resources cannot be forcibly taken away; they can only be released voluntarily. Circular wait — there exists a cycle of processes, each waiting for a resource held by the next. All four are necessary because deadlock requires that resources be exclusive, that a process be able to accumulate holdings while waiting, that no external force break the accumulation, and that the pattern of waits form a cycle. Removing any one condition makes deadlock impossible.

Question 2

A resource allocation graph contains a cycle. Does that guarantee deadlock? Explain.

Show answer
If every resource has exactly one instance, a cycle is necessary and sufficient for deadlock. If resources have multiple instances, a cycle is necessary but not sufficient: it indicates the possibility of deadlock, but the cycle may be breakable by allocating a spare instance of some resource on the cycle to a waiting process. In that case the system is not necessarily deadlocked.

Question 3

Why does imposing a global lock ordering break circular wait?

Show answer
Because a cycle requires at least two processes holding locks in opposite order. If every thread acquires locks according to a fixed global ordering — always ascending by lock ID, for example — then no thread can hold a lock with higher ID while waiting for one with lower ID. Any potential cycle would require an ascending chain to also descend, which the ordering forbids. The wait-for graph becomes acyclic by construction.
👤