Concurrency and parallelism
CP.21
Use `std::lock()` or `std::scoped_lock` to acquire multiple `mutex`es
Reason
To avoid deadlocks on multiple mutexes.
Example
This is asking for deadlock:
// thread 1
lock_guard<mutex> lck1(m1);
lock_guard<mutex> lck2(m2);
// thread 2
lock_guard<mutex> lck2(m2);
lock_guard<mutex> lck1(m1);
Instead, use lock():
// thread 1
lock(m1, m2);
lock_guard<mutex> lck1(m1, adopt_lock);
lock_guard<mutex> lck2(m2, adopt_lock);
// thread 2
lock(m2, m1);
lock_guard<mutex> lck2(m2, adopt_lock);
lock_guard<mutex> lck1(m1, adopt_lock);
or (better, but C++17 only):
// thread 1
scoped_lock<mutex, mutex> lck1(m1, m2);
// thread 2
scoped_lock<mutex, mutex> lck2(m2, m1);
Here, the writers of thread1 and thread2 are still not agreeing on the order of the mutexes, but order no longer matters.
Note
In real code, mutexes are rarely named to conveniently remind the programmer of an intended relation and intended order of acquisition. In real code, mutexes are not always conveniently acquired on consecutive lines.
Note
In C++17 it's possible to write plain
lock_guard lck1(m1, adopt_lock);
and have the mutex type deduced.
Enforcement
Detect the acquisition of multiple mutexes. This is undecidable in general, but catching common simple examples (like the one above) is easy.