Mutexes are synchronization primitives that allow you to manage concurrency. It is a common situation to have to lock more than one
mutex simultaneously to get access to several resources at the same time.
If this is not done properly, it can lead to deadlocks or crashes. If one thread acquires A and then tries to acquire B, while another thread
acquires B and then tries to acquire A, both threads will wait for each other forever.
In such a case, a commonly accepted good practice is to define an order on the mutexes then lock them in that order, and then unlock them
in the reverse order. However, such an order is not always clearly defined or easy to ensure across a whole program.
C++ provides facilities to lock multiple mutexes in one go, with a dedicated deadlock prevention algorithm. They should be used instead.
Before C++17, you should use std::lock
, and since C++17 you can use a variadic constructor of std::scoped_lock
. See the
examples for more details.
Noncompliant code example
void g();
std::mutex m1;
std::mutex m2;
void f() {
// The example would be the same with std::lock_guard if C++17 std::scoped_lock is not available
std::scoped_lock<std::mutex> lck1(m1); // Compliant: first mutex acquired
std::scoped_lock<std::mutex> lck2(m2); // Noncompliant: acquiring several mutexes
g();
}
Compliant solution
void g();
std::mutex m1;
std::mutex m2;
void f() { // Compliant: C++11 solution
std::lock(m1, m2);
std::lock_guard<std::mutex> lck1(m1, std::adopt_lock);
std::lock_guard<std::mutex> lck2(m2, std::adopt_lock);
g();
}
void f() { // Compliant: C++17 solution
std::scoped_lock<std::mutex, std::mutex> lck1(m1, m2);
g();
}