在Visual Studio 2012中std :: atomic_flag静态初始化线程是否安全?

Mic*_*ltu 5 c++ c++11 visual-studio-2012

Visual Studio 2012未实现用于线程安全静态初始化的C++ 11标准(http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2660.htm).我有一个函数本地静态,我需要保证将以线程安全的方式初始化.以下在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");
}
Run Code Online (Sandbox Code Playgroud)

Visual Studio 2012上的上述程序的输出很可能是:

0
5
Run Code Online (Sandbox Code Playgroud)

我需要解决这个问题,我试图找到一种方法,只使用函数本地静态(没有全局或类级静态).

我最初的想法是使用互斥锁,但它遇到了静态初始化线程安全的相同问题.如果我在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';
}
Run Code Online (Sandbox Code Playgroud)

在上面的代码中,两个线程是否有可能通过自旋锁,或者它是否只保证其中一个?不幸的是,我想不出一个简单的方法来测试这个,因为我不能在atomic_flag初始化器中放入一些东西来减慢速度,就像我可以用类一样.但是,我想确保我的程序在蓝色月亮中不会崩溃,因为我做了一个无效的假设.

Cor*_*son 5

C++ 11的第6.7.4节规定具有静态存储持续时间的变量是初始化的线程安全的:

如果控制在初始化变量时同时进入声明,则并发执行应等待初始化完成.

但VC++ 2012或2013 Preview都没有实现这一点,所以是的,你需要一些保护来使你的函数线程安全.

C++ 11 ATOMIC_FLAG_INIT在第29.7.4节中也说了这个:

ATOMIC_FLAG_INIT应以这样的方式定义宏,使其可用于将类型的对象初始化atomic_flag为清除状态.对于静态持续时间对象,该初始化应该是静态的.

VC++ 确实恰好实现了这一点.ATOMIC_FLAG_INIT0在VC++和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;
}
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

你可以在这里看到next_x绝对不是线程安全的,但next_x_ts永远不会初始化flag变量cs:dword_1400035D4- 它在应用程序启动时被零初始化,所以没有比赛并且next_x_ts是线程安全的.