NathanOliver's answer is not accurate: mu
is actually statically initialized – this means before any dynamic initialization, and therefore also before any user code could call mu.lock()
(whether directly or by using std::lock_guard<std::mutex>
).
Nonetheless, your use case is safe – in fact, std::mutex
initialization is even more safe than suggested by the previous answer.
The reason for this is that any variable with static storage duration (✓ check) that is initialized with a constant expression (where a call to a constexpr
constructor is explicitly considered as such – ✓ check), is constant initialized, which is a subset of static initialized. All static initialization happens strictly before all dynamic initialization, and hence before your function can be called the first time. (basic.start.static/2)
This applies to std::mutex
because std::mutex
has only one viable constructor, the default constructor, and it is specified to be constexpr
. (thread.mutex.class)
Therefore, in addition to the usual atomicity guarantee that C++11 and higher makes for dynamic initialization of static variables at function scope, other std::mutex
instances with static storage are also completely unaffected by initialization order issues, e.g.:
#include <mutex>
extern std::mutex mtx;
unsigned counter = 0u;
const auto count = []{ std::lock_guard<std::mutex> lock{mtx}; return ++counter; };
const auto x = count(), y = count();
std::mutex mtx;
If mtx
was dynamically initialized, this code would exhibit undefined behavior, because, then, mtx
's initializer would run after that of the dynamically initialized x
and y
, and mtx
would therefore be used before it is initialized.
(In pthread, or common implementations of <thread>
that use pthread, this effect is achieved by the use of the constant expression PTHREAD_MUTEX_INITIALIZER
.)
PS: This is also true for instances of std::atomic<T>
, as long as the argument passed to the constructor is a constant expression. This means e.g. that you can easily make a spin lock based on std::atomic<IntT>
that is immune to initialization order issues. std::once_flag
has the same desirable property. A std::atomic_flag
with static storage duration can also be statically initialized in either of two ways:
std::atomic_flag f;
, is zero-initialized (because of static storage duration and because it has a trivial default c'tor). Note that the state of the flag is nonetheless unspecified, which makes this approach rather useless.
std::atomic_flag f = ATOMIC_FLAG_INIT;
is constant initialized and unset. This is what you'd actually want to use.