Skip to the solution
MediumDSA11 min read

What is a deadlock in Python?

694 views

The problem

Question presented to candidate: "What is a deadlock? Show me how two Python threads can deadlock, how you would notice it in a running program, and how you design code so it cannot happen."

What a strong answer should cover:

  • A deadlock: a set of threads (or tasks, or processes) each waiting for a resource another one in the set holds, so none can ever proceed; the program hangs without an error.
  • The classic case: thread 1 holds lock A and waits for B, thread 2 holds B and waits for A β€” a cycle in the wait-for graph. The four Coffman conditions: mutual exclusion, hold and wait, no preemption, circular wait.
  • Python specifics: a plain threading.Lock is not re-entrant, so a thread that acquires it twice deadlocks itself; threading.RLock allows the owner to re-acquire.
  • Prevention: one global lock-acquisition order, holding one lock at a time, small critical sections; detection and recovery with acquire(timeout=...) and join(timeout=...).
  • Other Python forms: queue.join() with no consumer, a process whose output pipe fills up while the parent waits, asyncio tasks awaiting each other; faulthandler.dump_traceback or py-spy dump show where threads are stuck.

Code / implementation expected: Optional β€” two threads with opposite lock order, and the fix with a consistent order or timeouts.

  • threading
  • deadlock

Try it yourself

Sketch your approach before you read the solution. That is what the interview tests.

Stuck? Get a hint.

A short nudge toward the approach. It will not give the answer away.

Solution

The solution is hidden so you can try first

11 min read Β· 11 sections

Run the code

Edit the code and run it here. Each console line with an expected value becomes a test.

PythonSelf-deadlock, Lock vs RLock, wait-for cycles and ordered acquisition (no threads needed)
PythonA real lock-order deadlock between two threads, detected with timeouts and fixed (OS threads, not in the browser)
# A real lock-order deadlock between two threads, detected with timeouts,
# then fixed with a global lock order.
# Not runnable in the browser: needs OS threads.
import threading

lock_a = threading.Lock()
lock_b = threading.Lock()

def transfer(name, first, second, barrier, results):
    with first:
        barrier.wait()                       # both threads now hold one lock
        got = second.acquire(timeout=0.5)    # without a timeout: hangs forever
        if got:
            second.release()
        results[name] = "done" if got else "gave up (deadlock)"
        barrier.wait()                       # keep holding first until both tried

def run(order_1, order_2):
    results = {}
    barrier = threading.Barrier(2)
    t1 = threading.Thread(target=transfer, args=("T1", *order_1, barrier, results))
    t2 = threading.Thread(target=transfer, args=("T2", *order_2, barrier, results))
    t1.start(); t2.start()
    t1.join(); t2.join()
    return dict(sorted(results.items()))

# T1: a then b.  T2: b then a.  Each holds what the other needs.
print("opposite order:", run((lock_a, lock_b), (lock_b, lock_a)))

# Same order everywhere: one of them waits, but nobody waits in a circle.
def transfer_ordered(name, first, second, barrier, results):
    lo, hi = sorted((first, second), key=lambda l: 0 if l is lock_a else 1)
    with lo:
        with hi:
            results[name] = "done"

results = {}
ts = [threading.Thread(target=transfer_ordered, args=("T1", lock_a, lock_b, None, results)),
      threading.Thread(target=transfer_ordered, args=("T2", lock_b, lock_a, None, results))]
for t in ts: t.start()
for t in ts: t.join()
print("global order:  ", dict(sorted(results.items())))

# join() blocking forever is how a deadlocked program looks from outside.
stuck = threading.Lock()
stuck.acquire()
t = threading.Thread(target=stuck.acquire, daemon=True)  # will block forever
t.start()
t.join(timeout=0.3)
print("worker still alive after join(timeout=0.3):", t.is_alive())
stuck.release()   # let it go so the program can exit cleanly
t.join()
print("after release, alive:", t.is_alive())

Discussion

Other approaches, follow-ups and how it went in real interviews.

Sign in to join the discussion.

No comments yet. Share how you would answer it.

Question 76 of 139 in Python

Keep your momentum going.

Mark this question solved to track your progress, then take the next one while it is fresh.

All Python questions
Up nextMedium

What is the difference between the call stack and the heap in Python?

Continue