liveBook · Manning

Deadlock

As explained in C++ Concurrency in Action, Second Edition

Deadlock is a state in which two or more threads are indefinitely blocked, each waiting for a resource or event that another blocked thread must provide.

Deadlock occurs when threads wait on one another in a cycle, so none can make progress. A common case involves two mutexes: each thread holds one and waits to acquire the other. Unlike a race condition, the threads are not competing to act first; both are stuck waiting. section 3.2.4 section 3.2.3

Swapping two protected objects

Suppose each object has its own mutex, and swapping their data requires both locks. Locking the first argument before the second is not a consistent order: another thread can call the same swap with the arguments reversed. Each thread may then acquire a different mutex and wait forever for the other. section 3.2.4

Listing 3.6. Using std::lock() and std::lock_guard in a swap operation
class some_big_object;
void swap(some_big_object& lhs,some_big_object& rhs);
class X
{
private:
    some_big_object some_detail;
    std::mutex m;
public:
    X(some_big_object const& sd):some_detail(sd){}
    friend void swap(X& lhs, X& rhs)
    {
        if(&lhs==&rhs)
            return;
        std::lock(lhs.m,rhs.m);                                     #1
        std::lock_guard<std::mutex> lock_a(lhs.m,std::adopt_lock);  #2
        std::lock_guard<std::mutex> lock_b(rhs.m,std::adopt_lock);  #3
        swap(lhs.some_detail,rhs.some_detail);
    }
};


!@%STYLE%@!
{"css":"{\"css\": \"font-weight: bold;\"}","target":"[[{\"line\":13,\"ch\":68},{\"line\":13,\"ch\":70}],[{\"line\":14,\"ch\":68},{\"line\":14,\"ch\":70}],[{\"line\":15,\"ch\":68},{\"line\":15,\"ch\":70}]]"}
!@%STYLE%@!

The swap checks for the same object first, acquires both mutexes with std::lock, then gives each acquired lock to a std::lock_guard using std::adopt_lock before changing the data. section 3.2.4

When an operation needs multiple mutexes, std::lock acquires them together without deadlock during that acquisition. std::scoped_lock offers a shorter form for the swap. If locks must instead be acquired separately, use a fixed order that remains the same across callers—not an order based merely on argument position. section 3.2.4 section 3.2.5

Other ways to break or avoid a waiting cycle:

  • Avoid acquiring a lock while holding another when possible. Do not call user-supplied code under a lock without considering whether it might acquire one. section 3.2.5
  • Enforce an order when separate acquisitions are unavoidable. A hierarchical mutex can check lock levels and throw on an order violation. section 3.2.5
  • Look beyond mutexes: two threads that each join the other also deadlock, and a waiting cycle can involve more than two threads. section 3.2.5

A deadlocked task may never finish; if it serves the user interface, the interface may stop responding. Do not confuse this with livelock: livelocked threads keep running and checking, often consuming CPU, while still failing to make progress. section 11.1.1

How C++ Concurrency in Action, Second Edition covers it

How deep it goes
Works through a two-mutex swap and gives lock-order guidelines and a hierarchy-enforcement example.
Examples
Uses a reversed-argument swap, mutual thread joining, and a hierarchical mutex that rejects lock-order violations.
What it assumes
Builds the waiting-cycle idea from a concrete example while using basic C++ class, thread, and mutex syntax.
What you can do after
Apply it via std::lock or std::scoped_lock to acquire both locks for a two-object swap.
How it connects
Connects mutex locking and lock ordering to race conditions, thread joining, unwanted blocking, and livelock.

Also explained in

Compare how 15 books explain deadlock →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of C++ Concurrency in Action, Second Edition. The section links point to the parts of the book it draws on.