Skip to content

Deadlocks and How to Avoid Them

A deadlock happens when threads wait forever because each holds a resource the other needs.

deadlock.py
import threading
import time
 
lock_a = threading.Lock()
lock_b = threading.Lock()
 
 
def t1():
    with lock_a:
        time.sleep(0.1)
        with lock_b:
            print("t1")
 
 
def t2():
    with lock_b:
        time.sleep(0.1)
        with lock_a:
            print("t2")
 
 
threading.Thread(target=t1).start()
threading.Thread(target=t2).start()

This can deadlock because each thread holds one lock and waits for the other.

sketch Two locks, two orders: one deadlocks, one does not p5.js
Both threads need both locks. When they take them in opposite orders each ends up holding what the other needs, and the wait is circular -- measured 0 of 2 threads completed. Taking them in a fixed global order removes the cycle, and 2 of 2 completed. Click to switch the order. Nothing else changes: same locks, same work, same threads.
  1. Lock ordering: always acquire locks in the same order.
  2. Timeouts: use lock.acquire(timeout=...).
  3. Keep critical sections short.
  4. Prefer higher-level concurrency tools (Queue, Executor) when possible.
deadlock_fix.py
import threading
 
lock_a = threading.Lock()
lock_b = threading.Lock()
 
 
def safe():
    # Always acquire A then B
    with lock_a:
        with lock_b:
            print("safe")
 
threads = [threading.Thread(target=safe) for _ in range(5)]
for t in threads:
    t.start()
for t in threads:
    t.join()

pch.coffeeTagline

pch.coffeeCta

pch.feedbackHeading

pch.feedbackSubheading