Aggiungendo solo questa risposta perché penso che la risposta accettata potrebbe essere fuorviante. In tutti i casi dovrai bloccare il mutex, prima di chiamare notify_one () da qualche parte affinché il tuo codice sia thread-safe, sebbene potresti sbloccarlo di nuovo prima di chiamare effettivamente notify _ * ().
Per chiarire, DEVI prendere il lucchetto prima di entrare in wait (lk) perché wait () sblocca lk e sarebbe un comportamento indefinito se il lucchetto non fosse bloccato. Questo non è il caso di notify_one (), ma devi assicurarti di non chiamare notify _ * () prima di entrare in wait () e fare in modo che quella chiamata sblocchi il mutex; che ovviamente può essere fatto solo bloccando lo stesso mutex prima di chiamare notify _ * ().
Ad esempio, considera il seguente caso:
std::atomic_int count;
std::mutex cancel_mutex;
std::condition_variable cancel_cv;
void stop()
{
if (count.fetch_sub(1) == -999)
cv.notify_one();
}
bool start()
{
if (count.fetch_add(1) >= 0)
return true;
stop();
return false;
}
void cancel()
{
if (count.fetch_sub(1000) == 0)
return;
std::unique_lock<std::mutex> lk(cancel_mutex);
cancel_cv.wait(lk);
}
Attenzione : questo codice contiene un bug.
L'idea è la seguente: i thread chiamano start () e stop () in coppia, ma solo finché start () ha restituito true. Per esempio:
if (start())
{
stop();
}
Un (altro) thread ad un certo punto chiamerà cancel () e dopo essere tornato da cancel () distruggerà gli oggetti necessari in "Fai cose". Tuttavia, si suppone che cancel () non ritorni mentre ci sono thread tra start () e stop (), e una volta che cancel () ha eseguito la sua prima riga, start () restituirà sempre false, quindi nessun nuovo thread entrerà nel 'Do area roba.
Funziona bene?
Il ragionamento è il seguente:
1) Se un thread esegue con successo la prima riga di start () (e quindi restituirà true), nessun thread ha ancora eseguito la prima riga di cancel () (assumiamo che il numero totale di thread sia molto modo).
2) Inoltre, mentre un thread ha eseguito con successo la prima riga di start (), ma non ancora la prima riga di stop (), allora è impossibile che qualsiasi thread esegua con successo la prima riga di cancel () (nota che solo un thread ever calls cancel ()): il valore restituito da fetch_sub (1000) sarà maggiore di 0.
3) Una volta che un thread ha eseguito la prima riga di cancel (), la prima riga di start () restituirà sempre false e un thread che chiama start () non entrerà più nell'area "Fai cose".
4) Il numero di chiamate a start () e stop () è sempre bilanciato, quindi dopo che la prima riga di cancel () è stata eseguita senza successo, ci sarà sempre un momento in cui una (l'ultima) chiamata a stop () causa il conteggio per raggiungere -1000 e quindi notify_one () deve essere chiamato. Nota che può accadere solo quando la prima riga di annullamento ha provocato la caduta di quel thread.
A parte un problema di fame in cui così tanti thread chiamano start () / stop () che count non raggiunge mai -1000 e cancel () non ritorna mai, cosa che si potrebbe accettare come "improbabile e che non dura mai a lungo", c'è un altro bug:
È possibile che ci sia un thread all'interno dell'area 'Do stuff', diciamo che sta solo chiamando stop (); in quel momento un thread esegue la prima riga di cancel () leggendo il valore 1 con fetch_sub (1000) e cadendo. Ma prima che prenda il mutex e / o esegua la chiamata ad wait (lk), il primo thread esegue la prima riga di stop (), legge -999 e chiama cv.notify_one ()!
Quindi questa chiamata a notify_one () viene eseguita PRIMA di attendere () sulla variabile di condizione! E il programma si bloccherebbe indefinitamente.
Per questo motivo non dovremmo essere in grado di chiamare notify_one () fino a non chiamiamo wait (). Notare che la potenza di una variabile di condizione sta nel fatto che è in grado di sbloccare atomicamente il mutex, controllare se è avvenuta una chiamata a notify_one () e andare a dormire o meno. Non si può ingannare, ma si fare necessità di mantenere il mutex bloccato ogni volta che si apportano modifiche alle variabili che potrebbero cambiare la condizione da false a true e tenere chiusa a chiave durante la chiamata notify_one () a causa delle condizioni di gara, come descritto qui.
In questo esempio, tuttavia, non è presente alcuna condizione. Perché non ho utilizzato come condizione "count == -1000"? Perché questo non è affatto interessante qui: non appena viene raggiunto -1000, siamo sicuri che nessun nuovo thread entrerà nell'area 'Do stuff'. Inoltre, i thread possono ancora chiamare start () e incrementeranno il conteggio (a -999 e -998 ecc.) Ma non ci interessa. L'unica cosa che conta è che sia stato raggiunto -1000, quindi sappiamo per certo che non ci sono più thread nell'area "Fai cose". Siamo sicuri che questo sia il caso quando viene chiamato notify_one (), ma come assicurarci di non chiamare notify_one () prima che cancel () abbia bloccato il suo mutex? Il semplice blocco di cancel_mutex poco prima di notify_one () non aiuterà ovviamente.
Il problema è che, nonostante non stiamo aspettando una condizione, ci sono ancora è una condizione, e abbiamo bisogno di bloccare il mutex
1) prima che tale condizione sia raggiunta 2) prima di chiamare notify_one.
Il codice corretto diventa quindi:
void stop()
{
if (count.fetch_sub(1) == -999)
{
cancel_mutex.lock();
cancel_mutex.unlock();
cv.notify_one();
}
}
[... stesso inizio () ...]
void cancel()
{
std::unique_lock<std::mutex> lk(cancel_mutex);
if (count.fetch_sub(1000) == 0)
return;
cancel_cv.wait(lk);
}
Naturalmente questo è solo un esempio, ma altri casi sono molto simili; in quasi tutti i casi in cui si utilizza una variabile condizionale avrete bisogno di che il mutex sia bloccato (poco) prima di chiamare notify_one (), oppure è possibile che lo chiami prima di chiamare wait ().
Nota che ho sbloccato il mutex prima di chiamare notify_one () in questo caso, perché altrimenti c'è la (piccola) possibilità che la chiamata a notify_one () svegli il thread in attesa della variabile di condizione che poi proverà a prendere il mutex e block, prima di rilasciare nuovamente il mutex. È solo leggermente più lento del necessario.
Questo esempio era un po 'speciale in quanto la riga che cambia la condizione viene eseguita dallo stesso thread che chiama wait ().
Più usuale è il caso in cui un thread aspetta semplicemente che una condizione diventi vera e un altro thread prende il blocco prima di modificare le variabili coinvolte in quella condizione (facendola eventualmente diventare vera). In tal caso, il mutex viene bloccato immediatamente prima (e dopo) che la condizione si sia verificata, quindi è assolutamente ok sbloccare il mutex prima di chiamare notify _ * () in quel caso.
wait morphing
ottimizzazione) Regola empirica spiegata in questo collegamento: notificare WITH lock è meglio in situazioni con più di 2 thread per risultati più prevedibili.