2013-08-18 11 views
5

Visual Studio 2012 non implementa lo standard C++ 11 per l'inizializzazione statica thread-safe (http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2660.htm). Ho una funzione locale statica che devo garantire che verrà inizializzata in modo thread-safe. Di seguito è non thread-safe in Visual Studio 2012:Il thread di inizializzazione statica std :: atomic_flag è sicuro in 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"); 
} 

L'uscita del programma di cui sopra su Visual Studio 2012 sarà molto probabilmente:

0 
5 

ho bisogno di lavorare intorno a questo problema e sto cercando di trovare un modo per farlo solo con la funzione statica locale (nessuna statistica globale o di livello di classe).

Il mio primo pensiero era di usare un mutex, ma soffre dello stesso problema di sicurezza del thread di inizializzazione statica. Se ho uno static st :: mutex all'interno di foo è possibile che il secondo thread ottenga una copia del mutex mentre si trova in uno stato non valido.

Un'altra opzione è aggiungere uno spin-lock std :: atomic_flag. La domanda è, è std :: atomic_flag thread di inizializzazione sicuro in Visual Studio 2012?

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'; 
} 

Nel codice precedente, è possibile per entrambi i fili di superare la spin lock o è garantito che solo uno di essi? Sfortunatamente non riesco a pensare a un modo semplice per testarlo poiché non posso mettere qualcosa dentro l'inizializzatore atomic_flag per rallentarlo come faccio con una classe. Tuttavia, voglio essere sicuro che il mio programma non si bloccherà una volta in una luna blu perché ho fatto un'ipotesi non valida.

+0

Sono confuso da questo stesso problema. Dato che "la funzione statica locale" è la risposta classica al [fiasco statico di ordine init] (http://www.parashift.com/c++-faq/static-init-order.html), VS ci mette in una situazione molto stretta legare con questo! –

+0

La guardia spinlock è stato il modo in cui ho risolto il problema. Assicurati di includere la roba dell'ordine di memoria, altrimenti potresti ancora avere una condizione di competizione a causa del riordino della memoria da parte del compilatore/CPU! Una volta che l'inizializzazione è stata eseguita per la prima volta, il codice sopra riportato non dovrebbe quasi mai girare poiché acquisisce e cancella il blocco in pochissimi cicli. Se si trattasse di un pezzo di codice critico per le prestazioni, probabilmente si potrebbe fare meglio con un booleano non volatile che avvolge tutto ciò che passa da falso a vero (mai vero a falso) per evitare una potenziale sincronizzazione di base. –

risposta

5

sezione 6.7.4 di C++ 11 stabilisce che variabili con durata di conservazione statica vengono inizializzati thread-safe:

Se il controllo entra nella dichiarazione contemporaneamente mentre viene inizializzata la variabile, l'esecuzione concorrente deve attendere il completamento dell'inizializzazione.

Ma nessuno di VC++ 2012 o 2013 Anteprima implementa questo, quindi sì, avrete bisogno di una certa protezione per rendere la vostra funzione thread-safe.

C++ 11 dice anche questo circa ATOMIC_FLAG_INIT, in sezione 29.7.4:

La macro ATOMIC_FLAG_INIT sono definiti in modo tale che esso può essere utilizzato per inizializzare un oggetto di tipo atomic_flag allo stato chiaro. Per un oggetto a durata statica, tale inizializzazione deve essere statica.

VC++ non capita di implementare questo modo corretto. ATOMIC_FLAG_INIT è 0 in VC++ e VC++ zero-inizializza tutte le statistiche all'avvio dell'applicazione, non nella chiamata di funzione. Quindi, il tuo utilizzo di questo è sicuro e non ci sarà alcuna gara per inizializzare lock.

Codice di prova:

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 

Si può vedere qui che next_x non è sicuramente thread-safe, ma next_x_ts non inizializza la variabile flag a cs:dword_1400035D4 - è pari a zero-inizializzato all'avvio dell'applicazione, quindi non ci sono gare e next_x_ts è thread-safe.

+0

Lo standard afferma anche che l'inizializzazione statica è thread-safe, eppure non è in Visual Studio 2012.:/Idealmente mi piacerebbe sapere se il compilatore di Visual Studio 2012 corrisponde alle specifiche in questo caso, poiché non è in altri casi simili (inizializzatori statici). Se ATOMIC_FLAG_INIT era in C++ 03 mi sentirei più a mio agio. Essendo che il compilatore di Visual Studio 2012 implementa solo parte delle specifiche di C++ 11 e questo è nuovo nelle specifiche di C++ 11, mi sento piuttosto a disagio nel farci affidamento solo perché le specifiche dicono che posso farlo. –

+0

Funziona sicuramente - risposta aggiornata. –

+0

+1 per l'assemblaggio commentato. – ComicSansMS