2013-08-18 25 views
5

Visual Studio 2012未實施線程安全靜態初始化的C++ 11標準(http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2660.htm)。我有一個函數本地靜態,我需要保證將以線程安全的方式初始化。下面是在Visual Studio 2012 線程安全:std :: atomic_flag靜態初始化線程在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初始化程序中加入一些東西來減慢它,就像我可以用一個類一樣。但是,我想確保我的程序不會在藍色月亮中崩潰,因爲我做出了一個無效的假設。

+0

我對這個同樣的問題感到困惑。由於「函數本地靜態」是[static init order fiasco]的經典答案(http://www.parashift.com/c++-faq/static-init-order.html),VS讓我們非常緊張與此綁定! –

+0

螺旋鎖守衛是我最終如何解決問題。一定要包括內存順序的東西,否則,由於編譯器/ CPU的內存重新排序,您仍然可能會出現競爭狀態!第一次初始化完成後,上面的代碼幾乎不會旋轉,因爲它在非常少的幾個週期內獲取並清除鎖定。如果這是一個性能關鍵的代碼片段,那麼可以使用非易失性布爾封裝來實現更好的效果,從而避免潛在的核心同步,從而將整個事件從假轉換爲真(從不爲真)。 –

回答

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_INIT是在VC++中的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從不初始化flag變量cs:dword_1400035D4 - 它在應用程序啓動時被初始化爲零,所以沒有比賽,並且線程安全的是next_x_ts

+0

該標準還聲明靜態初始化是線程安全的,但它不在Visual Studio 2012中。:/理想情況下,我想知道在這種情況下Visual Studio 2012編譯器是否與規範相匹配,因爲它不在其他類似的情況(靜態初始化)。 如果ATOMIC_FLAG_INIT在C++ 03中,我會感覺更舒適。由於Visual Studio 2012編譯器只實現了C++ 11規範的一部分,而這在C++ 11規範中是新的,所以我只是因爲規範說我可以依靠它而非常不自在。 –

+0

它絕對有效 - 更新答案。 –

+0

+1用於評論大會。 – ComicSansMS

相關問題