2015-04-16 17 views
6

È sicuro rimuovere volatile dalla definizione di m_flag qui? Se m_flag non è volatile, cosa impedirebbe ai compilatori di ottimizzare le condizioni di questo ciclo: while (!m_flag) m_cv.wait(lock);? Lo standard (post-C++ 11) specifica esplicitamente che tali ottimizzazioni sono proibite in questi casi?È sicuro rimuovere il C++ volatile qui?

#include <mutex> 
#include <condition_variable> 
#include <future> 
#include <iostream> 
using namespace std; 

class foofoo 
{ 
    volatile bool m_flag; 
    mutex m_mutex; 
    condition_variable m_cv; 

public: 
    void DoWork() 
    { 
     m_flag = false; 
     unique_lock<mutex> lock(m_mutex); 
     auto junk = async(std::launch::async, [this]() 
     { 
      { 
       unique_lock<mutex> lock(m_mutex); 
       m_flag = true; 
      } 
      m_cv.notify_one(); 
     }); 
     while (!m_flag) m_cv.wait(lock); 
     cout << "ququ" << endl; 
    } 
}; 

int main() 
{ 
    foofoo f; 
    f.DoWork(); 
} 

risposta

10

In generale, volatile e multithreading sono ortogonali in C++ 11. L'utilizzo di volatile non aggiunge né rimuove le corse di dati.

In questo caso, è m_flag = true;sequenziati prima il rilascio del mutex nel filo lanciato da async ([intro.execution]/p14), che a sua volta sincronizza con la successiva acquisizione dell'indirizzo del mutex in m_cv.wait(lock) ([thread.mutex.requirements.mutex]/p11,25), che a sua volta è sequenziato prima una lettura successiva di m_flag. m_flag = true; pertanto inter-thread avviene prima dello e quindi avviene prima dello, la lettura successiva. ([Intro.multithread]/p13-14)

Poiché non ci sono altri effetti collaterali su m_flag, m_flag = true; è l'effetto lato visibile rispetto a quello letto ([intro.multithread]/p15), e che leggere deve quindi leggere ciò che è stato memorizzato dall'effetto collaterale visibile, ovvero true.

Un compilatore che "ottimizza" questa condizione, indipendentemente dal fatto che sia utilizzato, viene utilizzato il valore non conforme a volatile.

+0

Grazie! Quindi, in altre parole, se il corpo del ciclo non cambia la condizione del ciclo, ma contiene un "punto" di sincronizzazione, per ottimizzarlo, un compilatore dovrebbe analizzare se la condizione del ciclo potrebbe essere influenzata da un "lato visibile" effetto "da un altro thread che può sincronizzarsi con lo stesso" punto "? In pratica, i compilatori probabilmente si arrenderebbero e non tenterebbero mai di ottimizzare tali loop di distanza? .. –

+0

Intendo "ottimizzare le condizioni del loop". Mi sono appena reso conto che stavo dicendo "loop" ovunque, anche se ovviamente il loop sarebbe rimasto, basta trasformarlo in un loop infinito ... –

+2

@MaxGalkin Finché non scrivi una corsa di dati, il compilatore deve garantire che i cambiamenti fatto da un thread che in precedenza ha rilasciato un mutex è visibile a un thread che successivamente acquisisce lo stesso mutex. Non sono sicuro se direi "mai tentare di", ma finché il codice è libero da gare di dati, non ci dovrebbero essere sorprese con un compilatore conforme. –

1

Si ha una chiamata a wait all'interno del ciclo, quindi non è possibile per il compilatore eliminarlo. Oltre al fatto che il wait potrebbe essere una cosa più o meno opaca per il compilatore, contiene un blocco/sblocco di mutex al suo interno che impedisce efficacemente al compilatore qualsiasi eliminazione. Quindi volatile è totalmente inutile lì.

+0

Penso che se si usasse una variabile booleana locale in quel metodo invece di m_flag, il compilatore sarebbe libero di ottimizzare le condizioni del ciclo, perché la variabile locale non è visibile ad altri thread ... considerazioni sull'attesa "all'interno del corpo del loop ... –

+1

Se hai usato locale probabilmente lo farò - Non sono un esperto di compilatori. L'unico modo per il compilatore di eliminare qualsiasi cosa (a quanto ho capito) è provare che la variabile non può essere cambiata in nessun altro posto. Qualsiasi chiamata alla funzione opaca potrebbe ostacolare tremendamente questa capacità e un compilatore smetterebbe di provare. Tuttavia, se si fosse utilizzata la variabile locale, non ci sarebbe stata alcuna possibilità che fosse impostata in qualsiasi altro posto se non si fosse passato il suo indirizzo da nessuna parte. – ixSci

+0

Quindi questa affermazione della tua risposta non è corretta: "Hai una chiamata per" aspettare "all'interno del ciclo quindi non c'è modo per il compilatore di eliminarlo". Il solo fatto di avere una chiamata a "wait" non è sufficiente per impedire l'eliminazione ... –