Mutexes and deadlock

Two threads, two locks, opposite order: whether they deadlock depends only on which runs when. Pick which thread takes the next step, or let the simulator explore every possible interleaving and count how many end with everyone waiting.

Reachable states
23
Deadlocked states
1
Random scheduler deadlocks
50.0%
Program
Schedule
T1 takes A then B; T2 takes B then A. The textbook deadlock. Click a thread to run its next step.
Wait-for graph
T1 T2

An arrow from one thread to another means "waits for a lock it holds". A cycle is a deadlock.

Every interleaving
Distinct states
23
Complete schedules
6
…that deadlock
2
Can retry forever
no

    How to use the simulator

    Each card is a thread with its steps; the highlighted step runs next. Click a card to run that thread's next step, as if the scheduler picked it. A thread waiting for a lock someone else holds is blocked and cannot be clicked. The wait-for graph on the right updates after every step.

    • With Opposite order, click T1 once and T2 once: each holds one lock and wants the other's. Or press Shortest way to deadlock.
    • Switch to Same order and try to reach a deadlock: the exploration panel confirms there is no state where it happens.
    • Open Dining philosophers: of billions of possible schedules, only a few hundred deadlock, yet a scheduler picking threads at random deadlocks in 15.8% of runs.

    The exploration is a small model checker: it visits every state reachable by any choice of thread at every step. Its schedule counts match a brute-force walk over every interleaving, and every deadlock it finds has a cycle in the wait-for graph.

    What is a deadlock?

    A deadlock is a state in which a group of threads, processes or transactions are each waiting for something that another member of the group holds, so none of them can ever proceed. The classic case is two threads and two mutexes: thread 1 holds lock A and waits for B, thread 2 holds B and waits for A.

    Unlike most bugs, a deadlock does not corrupt anything; the program simply stops making progress, usually without an error message. It depends on timing, so it can pass every test and appear only under load.

    The four conditions

    Coffman, Elphick and Shoshani's 1971 survey "System Deadlocks" gave the conditions that must all hold for a deadlock:

    1. Mutual exclusion: a resource can be held by only one thread at a time.
    2. Hold and wait: a thread holds resources while waiting for more.
    3. No preemption: a resource cannot be taken from its holder; only the holder releases it.
    4. Circular wait: there is a cycle of threads, each waiting for a resource held by the next.

    Every prevention technique breaks one of them. Mutexes exist to provide mutual exclusion, so in practice the target is usually circular wait or hold and wait.

    Why deadlocks are rare and real

    A deadlock needs the threads to interleave in a particular way. The model checker counts the possibilities for each program on this page:

    ProgramStatesSchedulesDeadlocking schedulesRandom scheduler deadlocks
    Opposite order236250.0%
    Same order20200%
    Trylock and back off37unboundedunboundedn/a
    Three-way cycle111234633.3%
    Dining philosophers2,6232,430,842,12012015.8%
    Philosophers, one reversed2,2402,005,231,62000%

    Here every step counts the same, so a scheduler picking at random switches threads between the two lock calls very often. In a real program the window between taking the first lock and the second may be a few instructions in a time slice of milliseconds, so the bad interleaving might happen once in millions of runs, until a machine with more cores, a slower dependency or a new workload widens it. Exhaustive exploration of interleavings is how tools such as model checkers and systematic concurrency testers find these bugs before production does.

    Preventing deadlock

    • Lock ordering. Give every lock a rank and always acquire in increasing rank. No cycle can form, which removes circular wait. The fixed dining philosophers do exactly this: the last philosopher takes the higher-numbered fork first. For locks that have no natural order, such as two bank accounts, order them by an ID or address.
    • Trylock and back off. Take the first lock, try the second, and if it is busy release the first and start again. This breaks hold and wait, but two threads can keep colliding and retrying in step, a livelock; adding a random delay before retrying makes that unlikely.
    • Take everything at once, or use one coarser lock, at the cost of less concurrency.
    • Timeouts on lock acquisition turn a hang into an error the program can handle.
    • Avoidance: Dijkstra's banker's algorithm grants a request only if the system stays in a state where every thread can still finish. It needs to know maximum demands in advance, so it is rare outside textbooks.

    Detecting deadlock

    Systems that cannot rule deadlocks out detect them instead. Database engines build a wait-for graph among transactions and abort one transaction in any cycle; PostgreSQL runs the check once a transaction has waited longer than deadlock_timeout, one second by default. The Linux kernel's lockdep validator records the order in which every class of lock is ever taken and reports a possible deadlock the first time it sees two orders that could form a cycle, even if the deadlock did not happen on that run. The Go runtime aborts with "all goroutines are asleep - deadlock!" when no goroutine can run.

    History

    Edsger Dijkstra studied deadlock (he called it "deadly embrace") in the 1960s while designing the THE multiprogramming system, devised the banker's algorithm, and set the dining philosophers problem as an exercise. His 1971 paper "Hierarchical ordering of sequential processes" presents it: one semaphore per fork "contains the danger of the deadly embrace", and his fix lets a philosopher start eating only when neither neighbour is eating. Breaking the cycle by ordering resources was proposed by J. W. Havender at IBM in 1968. Coffman, Elphick and Shoshani's survey of the same year named the four necessary conditions.

    Deadlock questions and answers

    What are the four conditions for a deadlock?

    Coffman, Elphick and Shoshani listed them in 1971: mutual exclusion (a resource is held by one thread at a time), hold and wait (a thread holds one resource while waiting for another), no preemption (a resource cannot be taken away from its holder) and circular wait (a cycle of threads, each waiting for a resource the next one holds). All four must hold, so removing any one prevents deadlock.

    How do you prevent deadlocks?

    The usual method is a global lock order: if every thread acquires locks in the same order, a cycle of waits cannot form. Other options: take all the locks a task needs at once, use trylock and release everything on failure, use timeouts, or design the code so a thread never holds one lock while acquiring another.

    Why does a deadlock only happen sometimes?

    Because it needs a particular interleaving. In the two-thread example here, 2 of the 6 possible schedules deadlock, but when each thread holds its first lock for only a few instructions, real schedulers rarely switch at exactly that point. Five dining philosophers have 2,430,842,120 schedules, of which 120 deadlock. That is why deadlocks survive testing and appear under production load.

    What is a wait-for graph?

    A directed graph with an edge from each blocked thread to the thread holding the lock it wants. A deadlock is exactly a cycle in this graph. Databases such as PostgreSQL build it when a transaction has waited too long (deadlock_timeout, one second by default) and abort one transaction in the cycle.

    What is the difference between deadlock and livelock?

    In a deadlock the threads are blocked and nothing happens. In a livelock they keep running but make no progress, for instance by repeatedly taking a lock, failing to get a second one, releasing the first and trying again in step with each other. The trylock program on this page cannot deadlock, but its state graph contains cycles where it retries forever.

    What is the dining philosophers problem?

    Edsger Dijkstra’s illustration of deadlock: five philosophers sit around a table with one fork between each pair, and each needs both neighbouring forks to eat. If every philosopher picks up the left fork first, all five can end up holding one fork and waiting for another. Making one philosopher pick up the right fork first imposes an order on the forks and removes the deadlock.

    Further reading

    Published Updated