Is a mutex defined statically in a function body able to lock properly?
up vote
7
down vote
favorite
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);
...
}
c++ multithreading thread-safety mutex static-variables
add a comment |
up vote
7
down vote
favorite
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);
...
}
c++ multithreading thread-safety mutex static-variables
add a comment |
up vote
7
down vote
favorite
up vote
7
down vote
favorite
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);
...
}
c++ multithreading thread-safety mutex static-variables
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);
...
}
c++ multithreading thread-safety mutex static-variables
c++ multithreading thread-safety mutex static-variables
edited Nov 9 at 22:35
NathanOliver
83k15112173
83k15112173
asked Nov 9 at 22:13
Soimn
5315
5315
add a comment |
add a comment |
2 Answers
2
active
oldest
votes
up vote
2
down vote
accepted
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.
add a comment |
up vote
10
down vote
Yes, this is fine. The first time the function is called mu will be initialized (and this is guaranteed to be thread safe and only happen once) and then guard will lock it. If another thread calls foo it will wait at
std::lock_guard<std::mutex> guard(mu);
until the first call to foo completes and guard is destroyed unlocking mu.
add a comment |
2 Answers
2
active
oldest
votes
2 Answers
2
active
oldest
votes
active
oldest
votes
active
oldest
votes
up vote
2
down vote
accepted
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.
add a comment |
up vote
2
down vote
accepted
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.
add a comment |
up vote
2
down vote
accepted
up vote
2
down vote
accepted
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.
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.
edited Nov 10 at 14:54
answered Nov 10 at 14:34
Arne Vogel
3,74011125
3,74011125
add a comment |
add a comment |
up vote
10
down vote
Yes, this is fine. The first time the function is called mu will be initialized (and this is guaranteed to be thread safe and only happen once) and then guard will lock it. If another thread calls foo it will wait at
std::lock_guard<std::mutex> guard(mu);
until the first call to foo completes and guard is destroyed unlocking mu.
add a comment |
up vote
10
down vote
Yes, this is fine. The first time the function is called mu will be initialized (and this is guaranteed to be thread safe and only happen once) and then guard will lock it. If another thread calls foo it will wait at
std::lock_guard<std::mutex> guard(mu);
until the first call to foo completes and guard is destroyed unlocking mu.
add a comment |
up vote
10
down vote
up vote
10
down vote
Yes, this is fine. The first time the function is called mu will be initialized (and this is guaranteed to be thread safe and only happen once) and then guard will lock it. If another thread calls foo it will wait at
std::lock_guard<std::mutex> guard(mu);
until the first call to foo completes and guard is destroyed unlocking mu.
Yes, this is fine. The first time the function is called mu will be initialized (and this is guaranteed to be thread safe and only happen once) and then guard will lock it. If another thread calls foo it will wait at
std::lock_guard<std::mutex> guard(mu);
until the first call to foo completes and guard is destroyed unlocking mu.
edited Nov 9 at 22:22
answered Nov 9 at 22:16
NathanOliver
83k15112173
83k15112173
add a comment |
add a comment |
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53233931%2fis-a-mutex-defined-statically-in-a-function-body-able-to-lock-properly%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown