Skip to main content
edited body
Source Link
Cory Nelson
  • 30.3k
  • 5
  • 77
  • 114

You can see here that init_xnext_x is definitely not thread-safe, but init_x_tsnext_x_ts never initializes the flag variable at cs:dword_1400035D4 -- it is zero-initialized at application start, so there are no races and init_x_tsnext_x_ts is thread-safe.

You can see here that init_x is definitely not thread-safe, but init_x_ts never initializes the flag variable at cs:dword_1400035D4 -- it is zero-initialized at application start, so there are no races and init_x_ts is thread-safe.

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.

added 2906 characters in body
Source Link
Cory Nelson
  • 30.3k
  • 5
  • 77
  • 114

YouSection 6.7.4 of C++11 states that variables with static storage duration are correct ininitialized 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 reasoning that using a mutex will not workfunction thread-safe. Your initialization of

C++11 also says this about atomic_flagATOMIC_FLAG_INIT is safe. In the standard, in section 29.7.4:

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 init_x is definitely not thread-safe, but init_x_ts never initializes the flag variable at cs:dword_1400035D4 -- it is zero-initialized at application start, so there are no races and init_x_ts is thread-safe.

You are correct in your reasoning that using a mutex will not work. Your initialization of atomic_flag is safe. In the standard, section 29.7.4:

So there will be no race to initialize lock.

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:

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 init_x is definitely not thread-safe, but init_x_ts never initializes the flag variable at cs:dword_1400035D4 -- it is zero-initialized at application start, so there are no races and init_x_ts is thread-safe.

Source Link
Cory Nelson
  • 30.3k
  • 5
  • 77
  • 114

You are correct in your reasoning that using a mutex will not work. Your initialization of atomic_flag is safe. In the standard, 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.

So there will be no race to initialize lock.