Course outline · 0% complete

0/28 lessons0%

Course overview →

Deadlock: the standstill

lesson 5-4 · ~10 min · 18/28

The bug that does not crash — it just stops

Lesson 5-3 fixed races with locks, and mentioned the price in passing: deadlock. It deserves its own lesson because it is a bug class that produces no error message, no exception, and no crash — the program simply stops making progress forever, which in production looks like a service that suddenly answers nothing until someone restarts it. Databases, web servers, and GUI apps all suffer real outages from exactly this.

The recipe requires two locks and two threads:

  1. Thread 1 acquires lock A, then tries to acquire lock B.
  2. Thread 2 acquires lock B, then tries to acquire lock A.
  3. If the timing interleaves — each grabs its first lock before the other grabs its second — thread 1 waits for B (held by 2) while thread 2 waits for A (held by 1).

Neither can proceed until the other releases, and neither will ever release, because each is stuck waiting. This closed loop of "you first" is called a circular wait, and it is the defining ingredient of every deadlock.

The demo below forces the interleaving with sleeps, and uses acquire(timeout=...) so you can watch the standstill without your program actually hanging forever: after 1 second of waiting, each acquire gives up and reports False.

Code exercise · python

Run this. Each worker grabs one lock, sleeps so the other worker grabs the other lock, then tries to take the second lock with a 1-second timeout. Both time out: a deadlock, made visible instead of eternal.

Code exercise · python

Your turn: fix the deadlock with lock ordering. worker2 currently takes lock_b first, then waits for lock_a — the opposite order from worker1. Change worker2 to take lock_a first, then lock_b, the SAME order as worker1. With one agreed order, the circular wait becomes impossible and both workers finish.

The working defenses

Ranked by how often real teams use them:

  1. One agreed lock order. If every thread that needs multiple locks acquires them in the same fixed order (alphabetical, by ID — anything, as long as it is the same), a circular wait cannot form. This is what you just did, and it is the standard answer in interviews and code review.
  2. Hold one lock at a time. No second lock, no circle. Often achievable by shrinking the critical section or copying data out before taking the next lock.
  3. Timeouts plus retry. Acquire with a timeout; on failure, release everything, wait a moment, try again. Messier, but it turns an eternal hang into a recoverable slowdown.

Databases take a fourth path: they detect the cycle. PostgreSQL and MySQL watch who-waits-for-whom, and when they find a loop they kill one transaction with a deadlock detected error so the others can proceed. If you ever see that error in a backend log, you now know exactly what happened: two transactions locked rows in opposite orders.

Quiz

Thread 1 holds the accounts lock and waits for the audit-log lock. Thread 2 holds the audit-log lock and waits for the accounts lock. Which defense makes this scenario IMPOSSIBLE rather than just less likely?

Problem

Two threads each hold a lock the other needs, forming a closed loop of waiting that no one can exit. What is this situation called? (one word)