Is a mutex defined statically in a function body able to lock properly?

  • A+
Category:Languages

Is a mutex defined statically in a function body able to lock properly? I am currently using this pattern in my logger system, but I have not tested it's thread safety yet.

void foo () {     static std::mutex mu;     std::lock_guard<std::mutex> guard(mu);     ... } 

 


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.

Comment

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: