2013-04-19 5 views
20

Ho bisogno di impostare un flag per un altro thread per uscire. Quell'altro thread controlla di volta in volta il flag di uscita. Devo usare atomico per la bandiera o solo un semplice bool è sufficiente e perché (con un esempio di cosa esattamente potrebbe andare storto se uso il bool semplice)?Devo usare atomico <bool> per la variabile boolea "exit"?

#include <future> 
bool exit = false; 
void thread_fn() 
{ 
    while(!exit) 
    { 
     //do stuff 
     if(exit) break; 
     //do stuff 
    } 
} 
int main() 
{ 
    auto f = std::async(std::launch::async, thread_fn); 
    //do stuff 
    exit = true; 
    f.get(); 
} 

risposta

21

Devo usare atomica per la variabile bool “uscita”?

.

Utilizzare atomic<bool> oppure utilizzare la sincronizzazione manuale tramite (ad esempio) un std::mutex. Il tuo programma contiene attualmente una corsa dati , con una discussione che potenzialmente può leggere una variabile mentre un altro thread la sta scrivendo. Questo è un comportamento indefinito.

al comma 1.10/21 del C++ 11 standard:

L'esecuzione di un programma contiene una gara di dati se contiene due contrastanti azioni in diversi thread, almeno uno dei quali non è atomico e nessuno dei due avviene prima dell'altro. Qualsiasi risultato di tali dati risulta in comportamento non definito.

La definizione di "conflitto" è dato nel paragrafo 1.10/4:

due valutazioni espressione conflitto se uno di loro modifica una locazione di memoria (1.7) e l'altra accede o modifica la stessa posizione di memoria.

+0

Riesco a malapena a vedere come l'accesso a un "bool" possa essere tutt'altro che atomico nella pratica (sebbene io sia d'accordo in uno standard formale, è tutta un'altra questione). Ad ogni modo, a prescindere dalla condizione di competizione stessa, credo che occorra anche una barriera di memoria (fornita da 'atomic <>' o 'std :: mutex') per assicurare che il compilatore non memorizzi i dati nella cache o riordini le istruzioni. Tuttavia mi manca la conoscenza teorica per spiegarlo correttamente (anche se so come usarlo nella pratica), mi raccomando di illuminarmi se puoi? O dovrei creare una nuova domanda? – syam

+14

Per elaborare (in modo restrittivo), la condizione di race dei dati nello standard non riguarda solo ciò che un thread può fare che è visibile ad un altro thread. Riguarda anche l'ottimizzazione o il riordino del codice. Un compilatore non può assumere razze di dati, quindi può presumere che se un thread legge una variabile ma non la modifica, il valore non può cambiare tra i punti di sincronizzazione. Il tuo thread potrebbe non uscire mai. Oppure se un thread imposta un flag senza usarlo, la modifica può essere riordinata prima di qualsiasi altro codice (dopo l'ultimo punto di sincronizzazione). Sto persino ignorando la coerenza della cache. Questo è il motivo per cui l'atomica esiste. –

+0

@Andy cita esattamente al punto - abbastanza per me da usare l'atomico . Spero che Pete nella sua risposta chiarisca la descrizione dei problemi reali nel mio codice di esempio che può derivare dall'uso di bool semplice. – PowerGamer

11

Sì, si deve essere sincronizzato con. Il modo più semplice è, come dici tu, con atomic<bool>.

Formalmente, come dice @AndyProwl, la definizione del linguaggio dice che non usare un atomico qui dà un comportamento indefinito. Ci sono buone ragioni per questo.

In primo luogo, una lettura o scrittura di una variabile può essere interrotta a metà tramite un interruttore del filo; l'altro thread può vedere un valore parzialmente scritto, o se modifica il valore, il thread originale vedrà un valore misto. In secondo luogo, quando due thread vengono eseguiti su core diversi, hanno cache separate; scrivendo un valore lo memorizza nella cache, ma non aggiorna altre cache, quindi un thread potrebbe non vedere un valore scritto da un altro thread. Terzo, il compilatore può riorganizzare il codice in base a ciò che vede; nel codice di esempio, se nulla all'interno del ciclo modifica il valore di exit, il compilatore non ha motivo di sospettare che il valore cambierà; può trasformare il ciclo in while(1).

Atomica risolve tutti e tre questi problemi.

+1

Puoi approfondire la tua risposta un po 'di più: in che modo esattamente il primo problema può essere un problema per la variabile bool con due soli valori: 0 o 1 (non ci sono valori "mezzi")? 2 ° problema: stai dicendo che il thread che scrive in "exit" var può scrivere nella cache e il valore dalla cache non andrà mai nella memoria dove si trova var "exit"? 3 ° problema: vuoi dire che il compilatore può (in teoria) essere così intelligente da vedere che a) prima di chiamare a thread_fn() "exit" è sempre true eb) niente che thread_fn() chiama cambia "exit" - questo è ciò che dà al compilatore il diritto di cambiare il ciclo, corretto? – PowerGamer

+1

@PowerGamer - sì, la rottura di una variabile bool è improbabile. Non attraverserò scenari perversi in cui potrebbe ipoteticamente accadere. Per altri tipi può e avverrà se non si adottano misure per prevenirlo. –

-1

in realtà, niente è andato storto con il semplice bool in questo particolare esempio. l'unica nota è dichiarare la variabile di uscita bool come volatile per mantenerla in memoria. entrambe le architetture CISC e RISC implementano la lettura/scrittura bool come istruzione del processore rigorosamente atomico.anche i moderni processori multocore hanno implementato un'implementazione intelligente della cache. quindi, qualsiasi barriera di memoria non è necessaria. la citazione standard non è appropriata per questo caso particolare perché riguarda l'unica scrittura e la lettura dall'unico thread.

+1

x86 non aggiunge _define_ un tipo bool, quindi non può essere atomico. – MSalters