Visual Studio 2012没有为线程安全静态初始化(http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2660.htm)实现C ++ 11标准。我有一个函数本地静态,我需要保证将以线程安全的方式初始化。以下是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");
}
Visual Studio 2012上的上述程序的输出很可能是:
0
5
我需要解决这个问题,我正试图找到一种方法,只使用函数局部静态(没有全局或类级静态)。
我最初的想法是使用互斥锁,但它遇到了静态初始化线程安全的相同问题。如果我在foo中有一个静态的st :: mutex,那么第二个线程可能会在它处于无效状态时获得互斥锁的副本。
另一个选择是添加一个std :: atomic_flag自旋锁。问题是,Visual Studio 2012中的std :: atomic_flag初始化线程安全吗?
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';
}
在上面的代码中,两个线程是否有可能通过自旋锁或保证只有其中一个?不幸的是,我想不出一个简单的方法来测试这个,因为我不能在atomic_flag初始化器中放入一些东西来减慢速度,就像我可以用类一样。但是,我想确保我的程序在蓝月亮中不会崩溃,因为我做了一个无效的假设。
答案 0 :(得分:5)
C ++ 11的 6.7.4 部分规定具有静态存储持续时间的变量是初始化的线程安全的:
如果控件在初始化变量时同时进入声明,则并发执行应等待初始化完成。
但VC ++ 2012或2013 Preview都没有实现这一点,所以是的,您需要一些保护才能使您的函数保持线程安全。
C ++ 11在{strong> 29.7.4 :
部分中也提到ATOMIC_FLAG_INIT
。
宏
ATOMIC_FLAG_INIT
的定义方式可以用于将atomic_flag
类型的对象初始化为清除状态。对于静态持续时间对象,该初始化应该是静态的。
VC ++ 确实恰好实现了这一点。 VC ++中ATOMIC_FLAG_INIT
为0
,VC ++在应用程序启动时零初始化所有静态,而不是在函数调用中。因此,您对此的使用是安全的,并且没有竞争初始化lock
。
测试代码:
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
您可以在此处看到next_x
绝对不是线程安全的,但next_x_ts
永远不会在flag
初始化cs:dword_1400035D4
变量 - 它在应用程序中为零初始化开始,所以没有比赛,next_x_ts
是线程安全的。