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

时间:2013-08-18 22:07:30

标签: c++ visual-studio-2012 c++11

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初始化器中放入一些东西来减慢速度,就像我可以用类一样。但是,我想确保我的程序在蓝月亮中不会崩溃,因为我做了一个无效的假设。

1 个答案:

答案 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_INIT0,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是线程安全的。