5

Visual Studio 2012 は、スレッド セーフな静的初期化のための C++11 標準を実装していません ( http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2660.htm )。スレッドセーフな方法で初期化されることを保証する必要がある関数 local static があります。以下は、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 がある場合、2 番目のスレッドが無効な状態にある間にミューテックスのコピーを取得する可能性があります。

もう 1 つのオプションは、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 初期化子の中に何かを入れて速度を落とすことができないため、これをテストする簡単な方法は考えられません。ただし、無効な仮定を行ったために、ブルームーンでプログラムが一度もクラッシュしないようにしたいと考えています。

4

1 に答える 1

5

C++11 のセクション6.7.4は、静的ストレージ期間を持つ変数はスレッドセーフに初期化されると述べています。

変数の初期化中に制御が同時に宣言に入った場合、同時実行は初期化の完了を待ちます。

ただし、VC++ 2012 または 2013 Preview のどちらもこれを実装していないため、関数をスレッドセーフにするために何らかの保護が必要です。

C++11ATOMIC_FLAG_INITでは、セクション29.7.4で についても次のように述べています。

マクロは、タイプのオブジェクトをクリア状態ATOMIC_FLAG_INITに初期化するために使用できるように定義する必要があります。atomic_flag静的期間オブジェクトの場合、その初期化は静的でなければなりません。

VC++たまたまこれを適切に実装しています。ATOMIC_FLAG_INITVC++ にあり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初期化することはありません。アプリケーションの開始時にゼロで初期化されるため、競合がなく、スレッドセーフです。flagcs:dword_1400035D4next_x_ts

于 2013-08-19T00:29:16.797 に答える