Two threads transfer risk between books A and B, each locking the source book then the destination book. Under load the process hangs with both threads alive. State the four conditions that produced it, the ordering rule that removes it, and the standard library call that makes the rule unnecessary.
Two threads transfer risk between books A and B, each locking the source book then the destination book. Under load the process hangs with both threads alive. State the four conditions that produced it, the ordering rule that removes it, and the standard library call that makes the rule unnecessary.
Approach: Name the conditions a deadlock requires, then ask which one a total order over the resources removes and at what cost.
The four conditions are mutual exclusion, hold and wait, no preemption and circular wait, and imposing a total order on the mutexes, for example always locking the book with the lower id first, removes the circular wait, while std::scoped_lock over both mutexes makes the rule unnecessary because it applies a deadlock avoiding algorithm internally. The cycle here is thread one holding A and waiting for B while thread two holds B and waits for A. Lock ordering by a stable key costs one comparison and fails only where some path takes a lock while already holding one it never ordered against. A lock hierarchy assertion, where each mutex carries a level and taking a lower level while holding a higher one aborts in debug builds, is how the rule is enforced across a large codebase. Replacing the second lock with try_lock and a retry introduces livelock under contention and a worse latency tail.
Follow-up: How would you find the offending pair of acquisitions in a process that has already hung, without restarting it?
Key concepts: circular wait, lock ordering, deadlock, lock hierarchy.