5

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.

2
  • I am confounded by this same issue. Since "function-local statics" are the classic answer to the static init order fiasco, VS puts us in a really tight bind with this! Commented Dec 30, 2013 at 17:38
  • The spinlock guard was how I ultimately solved the problem. Be sure to include the memory order stuff or else you still could have a race condition due to memory reordering by the compiler/CPU! Once initialization is done the first time, the above code should almost never spin since it acquires and clears the lock in very few cycles. If this was a performance critical piece of code you could probably do better with a non-volatile boolean wrapping the whole thing that only goes from false to true (never true to false) to avoid potential core synchronization. Commented Dec 31, 2013 at 1:21

1 Answer 1

5

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 type atomic_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.

Sign up to request clarification or add additional context in comments.

1 Comment

The standard also states that static initialization is thread safe, yet it is not in Visual Studio 2012. :/ Ideally I would like to know whether or not the Visual Studio 2012 compiler matches the spec in this case, since it doesn't in other similar cases (static initializers). If ATOMIC_FLAG_INIT was in C++03 I would feel more comfortable. Being that the Visual Studio 2012 compiler only implements part of the C++11 spec and this is new in the C++11 spec I am quite uncomfortable relying on it just because the spec says I can.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.