141 lines
3.1 KiB
Plaintext
141 lines
3.1 KiB
Plaintext
== 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 {cpp}, 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
|
|
|
|
[source,c]
|
|
----
|
|
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:
|
|
|
|
[source,c]
|
|
----
|
|
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);
|
|
}
|
|
----
|
|
|
|
{cpp}03 solution:
|
|
|
|
[source,cpp]
|
|
----
|
|
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();
|
|
}
|
|
----
|
|
|
|
{cpp}11 and {cpp}14 solution:
|
|
|
|
[source,cpp]
|
|
----
|
|
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();
|
|
}
|
|
----
|
|
|
|
{cpp}17 and after:
|
|
|
|
[source,cpp]
|
|
----
|
|
std::mutex m1;
|
|
std::mutex m2;
|
|
|
|
void thread_safe_operation(void) {
|
|
std::scoped_lock lck1(m1, m2);
|
|
use_resources();
|
|
}
|
|
----
|
|
|
|
|
|
ifdef::env-github,rspecator-view[]
|
|
'''
|
|
== Comments And Links
|
|
(visible only on this page)
|
|
|
|
=== relates to: S5486
|
|
|
|
=== relates to: S5487
|
|
|
|
=== relates to: S5524
|
|
|
|
=== is related to: S5486
|
|
|
|
=== is related to: S5487
|
|
|
|
=== on 6 Nov 2019, 23:54:57 Loïc Joly wrote:
|
|
\[~geoffray.adde]
|
|
|
|
* Can you please review my changes?
|
|
* It's not clear to me if this rule is supposed to detect that in on place mtx1 is locked before mtx2, and in another place the order is reversed?
|
|
|
|
endif::env-github,rspecator-view[]
|