~/problems / Locks / Deadlock and lock ordering

Basics: take several locks without deadlock

easy basics ~10 min

Implement run_with_locks(locks, fn):

  • locks is a list of threading.Lock objects. Acquire all of them, call fn() while holding them, release them all, and return what fn() returned.
  • Many threads call it at once with overlapping locks listed in different orders. It must never deadlock.
  • If fn() raises, release every lock and let the exception propagate.
  • The same lock may appear more than once in locks; it must still be acquired only once (a Lock can't be acquired twice by the same thread). An empty list just calls fn().
a, b = threading.Lock(), threading.Lock()
# thread 1: run_with_locks([a, b], transfer)
# thread 2: run_with_locks([b, a], transfer)
# Taking them in the order given: 1 holds a and waits for b, 2 holds b and waits for a -> stuck forever.
run_with_locks([a, a], lambda: 7)   # 7

Constraints: up to 10 locks per call. Don't sleep, busy-wait or use one global lock for everything.

Show hint

A deadlock needs a cycle of waiting, so break "circular wait": remove duplicates and acquire the locks in one global order that every thread agrees on, such as sorted(set(locks), key=id).

Topic: Deadlock and lock ordering. The four conditions and how to break one.

0:00
Ctrl ' run · Ctrl ↵ submit
esc