2010-10-25 12 views
9

Sto sperimentando con il supporto C++ 0x e c'è un problema, suppongo che non dovrebbe esserci. O non capisco l'argomento o gcc ha un bug.Problemi di ordinamento della memoria

Ho il seguente codice, inizialmente x e sono uguali. Il thread 1 incrementa sempre x e quindi incrementa . Entrambi sono valori interi atomici, quindi non vi è alcun problema con l'incremento. Thread 2 sta verificando se il x è inferiore a e visualizza un messaggio di errore in tal caso.

Talvolta questo codice non riesce, ma perché? Il problema qui è probabilmente il riordino della memoria, ma tutte le operazioni atomiche sono sequenzialmente coerenti per impostazione predefinita e non mi sono esplicitamente rilassato di quelle operazioni. Sto compilando questo codice su x86, che per quanto ne so non dovrebbe avere problemi di ordinamento. Puoi spiegare per favore qual è il problema?

#include <iostream> 
#include <atomic> 
#include <thread> 

std::atomic_int x; 
std::atomic_int y; 

void f1() 
{ 
    while (true) 
    { 
     ++x; 
     ++y; 
    } 
} 

void f2() 
{ 
    while (true) 
    { 
     if (x < y) 
     { 
      std::cout << "error" << std::endl; 
     } 
    } 
} 

int main() 
{ 
    x = 0; 
    y = 0; 

    std::thread t1(f1); 
    std::thread t2(f2); 

    t1.join(); 
    t2.join(); 
} 

Il risultato può essere visualizzato here.

+0

In realtà questa è un'implementazione sperimentale di C++ 0x, quindi il secondo è possibile, ma credo che il primo sia più probabile: P – confucius

+3

Il codice pubblicato sopra produrrà sempre "errore" ', (' x' sarà sempre maggiore o uguale a 'y') è questo che volevi? – Paul

+0

Perché if (x confucius

risposta

11

Il problema potrebbe essere nel tuo test:

if (x < y) 

il filo potrebbe valutare x e non andare in giro a valutare y solo molto più tardi.

+0

Immagino che questo sia il problema, la domanda è: perché? Operazioni atomiche coerentemente sequenziali? deve impedire questo, no? – confucius

+0

Grazie mille! Il p roblem è una sequenza non specificata di valutazione dell'espressione, così semplice :) – confucius

+4

@confucius: mentre lo scenario potrebbe avere una dipendenza dall'ordine di lettura delle variabili, il problema più generale è che la lettura di 2 diverse istanze atomiche non è atomica. –

12

C'è un problema con il confronto:

x < y 

L'ordine di valutazione di sottoespressioni (in questo caso, di x e y) è specificata, quindi y può essere valutata prima x o x può essere valutato prima di .

Se x viene letto prima, hai un problema:

x = 0; y = 0; 
t2 reads x (value = 0); 
t1 increments x; x = 1; 
t1 increments y; y = 1; 
t2 reads y (value = 1); 
t2 compares x < y as 0 < 1; test succeeds! 

Se si garantisce esplicitamente che y viene letto prima, è possibile evitare il problema:

int yval = y; 
int xval = x; 
if (xval < yval) { /* ... */ } 
+0

Grazie! Questo è tutto. Scusate ragazzi, non posso più, https è bloccato in ufficio e non riesco ad accedere :( – confucius

-3

In primo luogo, sono d'accordo con "Michael Burr" e "James McNellis". Il tuo test non è giusto, e c'è una possibilità legale di fallire. Tuttavia, anche se si riscrive il test nel modo in cui "James McNellis" suggerisce che il test potrebbe fallire.

Il primo motivo per questo è che non si usa la semantica volatile, quindi il compilatore può fare ottimizzazioni sul proprio codice (che dovrebbero essere ok in un caso a thread singolo).

Ma anche con volatile il tuo codice non è garantito per funzionare.

Penso che tu non comprenda completamente il concetto di riordino della memoria . In realtà il riordino della memoria può avvenire a due livelli:

  1. Il compilatore può scambiare l'ordine delle istruzioni di lettura/scrittura generate.
  2. La CPU può eseguire le istruzioni di lettura/scrittura della memoria in ordine arbitrario.

L'utilizzo di volatile impedisce il (1). Tuttavia non hai fatto nulla per evitare (2) - riordino dell'accesso alla memoria con l'hardware .

Per evitare questo, è necessario inserire le istruzioni speciali recinzione di memoria nel codice (che sono designate per CPU, a differenza di volatile che è solo per il compilatore).

In x86/x64 ci sono molte istruzioni di recinzione di memoria diverse. Inoltre, tutte le istruzioni con la semantica lock per impostazione predefinita rilevano la memoria piena.

Maggiori informazioni qui:

http://en.wikipedia.org/wiki/Memory_barrier

+2

Gli atomici C++ 0x garantiscono l'ordine di memoria corretto –

+1

valdo - non c'è bisogno di usare volatile qui, perché le barriere di memoria di default generate da C++ 0x operazioni atomiche impediscono entrambi (1) e (2). – confucius

4

Ogni tanto, x sarà avvolgere intorno a 0 appena prima y avvolge intorno a zero. A questo punto, sarà legittimamente maggiore di x.

+2

Abbiamo provato a modificarlo.Potrebbe anche essere notato che l'overflow firmato porta a un comportamento indefinito, anche se l'overflow non firmato si adatta come previsto – GManNickG

+0

Accetto. Dovrebbe succedere, è stato fatto solo per il test, ma potrebbe anche essere un problema. Grazie. – confucius