Why is this an issue?
Mutexes are synchronization primitives that allow the managing of concurrency. It is a common situation to have to use multiple
mutexes to protect multiple resources with different access patterns.
In such a situation, it is crucial to define an order on the set of all mutexes:
- This order should be strictly followed when locking mutexes.
- The reverse order should be strictly followed when unlocking mutexes.
Failure to do so can lead to deadlocks. i.e., situations where two or more threads are blocked forever, each holding one mutex and waiting
for one held by the other(s).
In C++, an easy way to make sure the unlocks are called in reverse order from the lock is to wrap the lock/unlock operations in an RAII class
(since destructors of local variables are called in reverse order of their creation).
If instead of pthread_mutex_t
you are using std::mutex
, there are other mechanisms that allow you to avoid deadlocks in
that case, see S5524.
How to fix it
Reorder locking and unlocking operations to always lock in the same order and unlock in the reverse order.
Code examples
Noncompliant code example
pthread_mutex_t mtx1;
pthread_mutex_t mtx2;
void thread_safe_operation(void) {
pthread_mutex_lock(&mtx1);
pthread_mutex_lock(&mtx2);
use_resources();
pthread_mutex_unlock(&mtx1); // Noncompliant
pthread_mutex_unlock(&mtx2);
}
Compliant solution
C solution:
pthread_mutex_t mtx1;
pthread_mutex_t mtx2;
void thread_safe_operation(void) {
pthread_mutex_lock(&mtx1);
pthread_mutex_lock(&mtx2);
use_resources();
pthread_mutex_unlock(&mtx2);
pthread_mutex_unlock(&mtx1);
}
C++03 solution:
pthread_mutex_t mtx1;
pthread_mutex_t mtx2;
struct MutexLocker {
MutexLocker(pthread_mutex_t* mtx) : mtx(mtx) {
pthread_mutex_lock(mtx);
}
~MutexLocker() {
pthread_mutex_unlock(mtx);
}
pthread_mutex_t* mtx;
};
struct ResourcesLocker {
ResourcesLocker() : m1(&mtx1), m2(&mtx2) {}
MutexLocker m1;
MutexLocker m2;
};
void thread_safe_operation(void) {
ResourcesLocker locker;
use_resources();
}
C++11 and C++14 solution:
std::mutex m1;
std::mutex m2;
void thread_safe_operation(void) {
std::lock(m1, m2);
std::lock_guard<std::mutex> lck1(m1, std::adopt_lock);
std::lock_guard<std::mutex> lck2(m2, std::adopt_lock);
use_resources();
}
C++17 and after:
std::mutex m1;
std::mutex m2;
void thread_safe_operation(void) {
std::scoped_lock lck1(m1, m2);
use_resources();
}