简体   繁体   English

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

[英]Is std::atomic_flag static initialization thread safe in Visual Studio 2012?

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 ). Visual Studio 2012并未为线程安全的静态初始化实现C ++ 11标准( 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: 以下不是 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: 上述程序在Visual Studio 2012上的输出很可能是:

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. 如果我在foo中有一个静态的st :: mutex,则第二个线程处于无效状态时有可能会获得该互斥体的副本。

Another option is to add an std::atomic_flag spin-lock. 另一种选择是添加一个std :: atomic_flag自旋锁。 The question is, is std::atomic_flag initialization thread safe in Visual Studio 2012? 问题是,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';
}

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. 不幸的是,我无法想到一种简单的方法来测试它,因为我无法像在类中那样在atomic_flag初始化程序中放一些东西来使其减慢速度。 However, I want to be sure that my program won't crash once in a blue moon because I made an invalid assumption. 但是,我想确保我的程序不会在一次蓝月亮时崩溃,因为我做了一个无效的假设。

Section 6.7.4 of C++11 states that variables with static storage duration are initialized thread-safe: C ++ 11的6.7.4节指出,具有静态存储持续时间的变量已初始化为线程安全的:

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. 但是VC ++ 2012或2013 Preview均未实现此功能,因此,是的,您需要某种保护以使函数具有线程安全性。

C++11 also says this about ATOMIC_FLAG_INIT , in section 29.7.4 : C ++ 11在第29.7.4节中还说了关于ATOMIC_FLAG_INIT 内容

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. ATOMIC_FLAG_INIT定义方式应可用于将atomic_flag类型的对象初始化为清除状态。 For a static-duration object, that initialization shall be static. 对于静态对象,该初始化应为静态。

VC++ does happen to implement this properly. VC ++ 确实可以正确实现此目的。 ATOMIC_FLAG_INIT is 0 in VC++, and VC++ zero-initializes all statics at application start, not in the function call. 在VC ++中, ATOMIC_FLAG_INIT0 ,而VC ++在应用程序启动时(而不是在函数调用中)对所有静态变量进行零初始化。 So, your use of this is safe and there will be no race to initialize lock . 因此,您对此的使用是安全的,并且不会竞争初始化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 : 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 : 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. 您可以在此处看到next_x绝对不是线程安全的,但是next_x_ts永远不会在cs:dword_1400035D4初始化flag变量-它在应用程序启动时被初始化为零,因此不存在任何next_x_ts并且next_x_ts是线程安全的。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM