Mutexes are synchronization primitives that allow managing concurrency using a mechanism of lock
/unlock
.
While explicitly locking or unlocking a mutex is possible, it is error-prone. This is particularly true in complex code paths (or with
exceptions) where it is easy to have a mismatch between lock
s and unlock
s.
As a result, mutexes should not be locked or unlocked manually.
Adopting the C++ RAII (Resource Acquisition Is Initialization) idiom solves this problem by creating an object that will lock the
mutex on creation and unlock it on destruction. Furthermore, using this idiom can also greatly improve the readability of the code.
Several classes are available as RAII wrappers:
-
std::scoped_lock
is the default, most efficient wrapper for simple cases (only available since C++17)
-
std::lock_guard
is similar to std::scoped_lock
, but with fewer features. It should only be used if you don’t have
access to std::scoped_lock
.
-
std::unique_lock
allows more manual unlocking/locking again and should only be used when these features are needed, for instance,
with condition variables.
Noncompliant code example
#include <mutex>
class DataItem;
class DataStore {
public:
bool store(const DataItem &dataItem);
bool has(const DataItem &dataItem);
};
DataStore sharedDataStore;
std::mutex sharedDataStoreMutex;
bool storeIfRelevantInSharedContext(const DataItem &dataItem) {
sharedDataStoreMutex.lock(); // Noncompliant
if (sharedDataStore.has(dataItem)) {
sharedDataStoreMutex.unlock(); // Noncompliant
return false;
}
bool result = sharedDataStore.store(dataItem);
sharedDataStoreMutex.unlock(); // Noncompliant
return result;
}
Compliant solution
#include <mutex>
class DataItem;
class DataStore {
public:
bool store(const DataItem &dataItem);
bool has(const DataItem &dataItem);
};
DataStore sharedDataStore;
std::mutex sharedDataStoreMutex;
bool storeIfRelevantInSharedContext(const DataItem &dataItem) {
std::scoped_lock<std::mutex> lock(sharedDataStoreMutex);
if (sharedDataStore.has(dataItem)) {
return false;
}
return sharedDataStore.store(dataItem);
}