10

私はこの質問に対する答えを求めてSOとMSDNの周りを探してきましたが、明確で最終的な答えを見つけることができないようです...

それが C++11 標準にあり、現在の GCC バージョンがこのように動作することは知っていますが、VC2010 は現在、ローカル静的変数の初期化のスレッドセーフを保証していますか?

ie: これは VC2010 でスレッドセーフですか?

    static S& getInstance()
    {
        static S instance;
        return instance;
    }

...そうでない場合、VC2010 を使用して C++ でスレッドセーフなシングルトンを実装するための現在のベスト プラクティスは何ですか?

編集: Chris Betti の回答で指摘されているように、VC2010 はローカル静的変数 init のスレッドセーフを実装していません。

4

2 に答える 2

11

Static に関するVisual Studio 2010のドキュメントから:

マルチスレッド アプリケーションで静的ローカル変数に値を代入することは、スレッド セーフではないため、プログラミングの実践としてはお勧めしません。

質問の 2 番目の部分には、既存の優れた回答がいくつかあります。

2015 年 11 月 22 日更新:

他の人は、具体的には、静的初期化もスレッドセーフではないことを確認しています(コメントと他の回答を参照)。

VS2015 のユーザー squelart:

VS2015 が最終的に正しくなることを追加することもできます: https://msdn.microsoft.com/en-au/library/hh567368.aspx#concurrencytable ("Magic statics")

于 2012-05-14T15:09:35.327 に答える
7

次のコード スニペットは、「ローカル スコープの静的オブジェクトの初期化」がスレッドセーフではないことを示しています。

#include <windows.h>
#include <stdio.h>
#include <process.h>
struct X {
    ~X() { puts("~X()"); }
    int i_ ;
    void print(void) {
        printf("thread id=%u, i = %d\n", GetCurrentThreadId(), i_);
    }
    X(int i) {
        puts("begin to sleep 10 seconds");
        Sleep(1000 * 10);
        i_ = i;
        printf("X(int) i = %d\n", i_);
        puts("end");
    }
};

X & getX()
{
    static X static_x(1000);
    return static_x;
}

void thread_proc(void *)
{
    X & x = getX();
    x.print();
}

int main(int argc, char *argv[])
{
    HANDLE all_threads[2] = {};
    all_threads[0] = HANDLE( _beginthread(thread_proc, 0, 0) );
    printf("First thread Id: %u\n", GetThreadId(all_threads[0]) );
    Sleep(1000);
    all_threads[1] = HANDLE( _beginthread(thread_proc, 0, 0) );
    printf("Second thread Id: %u\n", GetThreadId(all_threads[1]) );
    WaitForMultipleObjects( _countof(all_threads), all_threads, TRUE, 1000 * 20);
    puts("main exit");
    return 0;
}

出力は次のようになります (もちろん、スレッド ID はマシンによって異なります)。

First thread Id: 20104
begin to sleep 10 seconds
Second thread Id: 20248
thread id=20248, i = 0
X(int) i = 4247392
end
thread id=20104, i = 1000
main exit
~X()

シングルトンの ctor が呼び出されて返されることを意味する最初のスレッドが戻る前に、2 番目のスレッドは初期化されていないオブジェクトを取得し、そのメンバー メソッドを呼び出します (静的オブジェクトは BSS セグメントにあるため、ローダーがロードした後にゼロに初期化されます)。実行可能)、間違った値: 0 を取得します。

/FAsc /Fastatic.asm によるアセンブリ リストをオンにすると、関数 getX() のアセンブリ コードが取得されます。

01:  ?getX@@YAAAUX@@XZ PROC                 ; getX
02:  
03:  ; 20   : {
04:  
05:    00000    55       push    ebp
06:    00001    8b ec        mov     ebp, esp
07:  
08:  ; 21   :   static X static_x(1000);
09:  
10:    00003    a1 00 00 00 00   mov     eax, DWORD PTR ?$S1@?1??getX@@YAAAUX@@XZ@4IA
11:    00008    83 e0 01     and     eax, 1
12:    0000b    75 2b        jne     SHORT $LN1@getX
13:    0000d    8b 0d 00 00 00
14:     00       mov     ecx, DWORD PTR ?$S1@?1??getX@@YAAAUX@@XZ@4IA
15:    00013    83 c9 01     or  ecx, 1
16:    00016    89 0d 00 00 00
17:     00       mov     DWORD PTR ?$S1@?1??getX@@YAAAUX@@XZ@4IA, ecx
18:    0001c    68 e8 03 00 00   push    1000           ; 000003e8H
19:    00021    b9 00 00 00 00   mov     ecx, OFFSET ?static_x@?1??getX@@YAAAUX@@XZ@4U2@A
20:    00026    e8 00 00 00 00   call    ??0X@@QAE@H@Z      ; X::X
21:    0002b    68 00 00 00 00   push    OFFSET ??__Fstatic_x@?1??getX@@YAAAUX@@XZ@YAXXZ ; `getX'::`2'::`dynamic atexit destructor for 'static_x''
22:    00030    e8 00 00 00 00   call    _atexit
23:    00035    83 c4 04     add     esp, 4
24:  $LN1@getX:
25:  
26:  ; 22   :   return static_x;
27:  
28:    00038    b8 00 00 00 00   mov     eax, OFFSET ?static_x@?1??getX@@YAAAUX@@XZ@4U2@A
29:  
30:  ; 23   : }

10 行目の暗号記号 [?$S1@?1??getX@@YAAAUX@@XZ@4IA] は、シングルトンが ctored かどうかを示すグローバル インジケータ (BSS にもあります) であり、true としてフラグが立てられます。 ctor を呼び出す直前の 14 ~ 17 行目で、これが問題です。これは、2 番目のスレッドが初期化されていないシングルトン オブジェクトをすぐに取得し、喜んでそのメンバー関数を呼び出す理由も説明しています。コンパイラによって挿入されるスレッド セーフ関連のコードはありません。

于 2012-06-21T01:32:36.420 に答える