Why is this an issue?
Mutexes are synchronization primitives that allow to manage concurrency. It is a common situation to have to lock multiple
mutexes 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 then tries to acquire B, while another thread acquires
B 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 and to lock them in that order and 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();
}
Resources