Visual Studio 2012 does not implement the C++11 standard for thread safe static initialization (http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2660.htm). I have a function local static that I need to guarantee will be initialized in a thread safe way. The following is not thread safe in Visual Studio 2012:
struct MyClass
{
int a;
MyClass()
{
std::this_thread::sleep_for(std::chrono::milliseconds(100));
a = 5;
}
};
void foo()
{
static MyClass instance;
std::cout << instance.a << '\n';
}
int main()
{
std::thread a(foo);
std::thread b(foo);
a.join();
b.join();
system("pause");
}
The output of the above program on Visual Studio 2012 will most likely be:
0
5
I need to work around this problem and I am trying to find a way to do it with function local statics only (no globals or class level statics).
My initial thought was to use a mutex, but it suffers from the same problem of static initialization thread safety. If I have a static st::mutex inside of foo it is possible that the second thread will get a copy of the mutex while it is in an invalid state.
Another option is to add an std::atomic_flag spin-lock. The question is, is std::atomic_flag initialization thread safe in Visual Studio 2012?
void foo()
{
// is this line thread safe?
static std::atomic_flag lock = ATOMIC_FLAG_INIT;
// spin lock before static construction
while (lock.test_and_set(std::memory_order_acquire));
// construct an instance of MyClass only once
static MyClass instance;
// end spin lock
lock.clear(std::memory_order_release);
// the following is not thread safe
std::cout << instance.a << '\n';
}
In the above code, is it possible for both threads to get past the spin lock or is it guaranteed that only one of them will? Unfortunately I can't think of an easy way to test this since I can't put something inside the atomic_flag initializer to slow it down like I can with a class. However, I want to be sure that my program won't crash once in a blue moon because I made an invalid assumption.
Section 6.7.4 of C++11 states that variables with static storage duration are initialized thread-safe:
If control enters the declaration concurrently while the variable is being initialized, the concurrent execution shall wait for completion of the initialization.
But neither of VC++ 2012 or 2013 Preview implement this, so yes, you'll need some protection to make your function thread-safe.
C++11 also says this about ATOMIC_FLAG_INIT
, in section 29.7.4:
The macro
ATOMIC_FLAG_INIT
shall be defined in such a way that it can be used to initialize an object of typeatomic_flag
to the clear state. For a static-duration object, that initialization shall be static.
VC++ does happen to implement this properly. ATOMIC_FLAG_INIT
is 0
in VC++, and VC++ zero-initializes all statics at application start, not in the function call. So, your use of this is safe and there will be no race to initialize lock
.
Test Code:
struct nontrivial
{
nontrivial() : x(123) {}
int x;
};
__declspec(dllexport) int next_x()
{
static nontrivial x;
return ++x.x;
}
__declspec(dllexport) int next_x_ts()
{
static std::atomic_flag flag = ATOMIC_FLAG_INIT;
while(flag.test_and_set());
static nontrivial x;
flag.clear();
return ++x.x;
}
next_x
:
mov eax, cs:dword_1400035E4
test al, 1 ; checking if x has been initialized.
jnz short loc_140001021 ; if it has, go down to the end.
or eax, 1
mov cs:dword_1400035E4, eax ; otherwise, set it as initialized.
mov eax, 7Bh
inc eax ; /O2 is on, how'd this inc sneak in!?
mov cs:dword_1400035D8, eax ; init x.x to 124 and return.
retn
loc_140001021:
mov eax, cs:dword_1400035D8
inc eax
mov cs:dword_1400035D8, eax
retn
next_x_ts
:
loc_140001032:
lock bts cs:dword_1400035D4, 0 ; flag.test_and_set().
jb short loc_140001032 ; spin until set.
mov eax, cs:dword_1400035E0
test al, 1 ; checking if x has been initialized.
jnz short loc_14000105A ; if it has, go down to end.
or eax, 1 ; otherwise, set is as initialized.
mov cs:dword_1400035E8, 7Bh ; init x.x with 123.
mov cs:dword_1400035E0, eax
loc_14000105A:
lock btr cs:dword_1400035D4, 0 ; flag.clear().
mov eax, cs:dword_1400035E8
inc eax
mov cs:dword_1400035E8, eax
retn
You can see here that next_x
is definitely not thread-safe, but next_x_ts
never initializes the flag
variable at cs:dword_1400035D4
-- it is zero-initialized at application start, so there are no races and next_x_ts
is thread-safe.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With