Apenas adicionando esta resposta porque acho que a resposta aceita pode ser enganosa. Em todos os casos, você precisará bloquear o mutex, antes de chamar notificar_one () em algum lugar para que seu código seja thread-safe, embora você possa desbloqueá-lo novamente antes de realmente chamar notificar _ * ().
Para esclarecer, você DEVE fazer o bloqueio antes de entrar em wait (lk) porque wait () desbloqueia lk e seria um comportamento indefinido se o bloqueio não estivesse bloqueado. Este não é o caso de notificar_one (), mas você precisa ter certeza de não chamar notificar _ * () antes de inserir wait () e fazer com que essa chamada desbloqueie o mutex; o que, obviamente, só pode ser feito bloqueando o mesmo mutex antes de chamar notificar _ * ().
Por exemplo, considere o seguinte 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);
}
Aviso : este código contém um bug.
A ideia é a seguinte: threads chamam start () e stop () em pares, mas apenas enquanto start () retornar true. Por exemplo:
if (start())
{
stop();
}
Um (outro) thread em algum ponto chamará cancel () e após retornar de cancel () destruirá os objetos que são necessários em 'Fazer coisas'. No entanto, cancel () não deve retornar enquanto houver threads entre start () e stop (), e uma vez que cancel () execute sua primeira linha, start () sempre retornará falso, então nenhuma nova thread entrará no 'Do área de coisas.
Funciona certo?
O raciocínio é o seguinte:
1) Se qualquer thread executar com sucesso a primeira linha de start () (e, portanto, retornará true), então nenhuma thread executou a primeira linha de cancel () ainda (assumimos que o número total de threads é muito menor que 1000 em caminho).
2) Além disso, enquanto uma thread executou com sucesso a primeira linha de start (), mas ainda não a primeira linha de stop (), então é impossível que qualquer thread execute com sucesso a primeira linha de cancel () (note que apenas uma thread sempre chama cancel ()): o valor retornado por fetch_sub (1000) será maior que 0.
3) Uma vez que um thread tenha executado a primeira linha de cancel (), a primeira linha de start () sempre retornará falso e um thread chamando start () não entrará mais na área 'Do stuff'.
4) O número de chamadas para iniciar () e parar () são sempre balanceadas, então após a primeira linha de cancel () ser executada sem sucesso, sempre haverá um momento em que uma (última) chamada para parar () causa contagem para atingir -1000 e, portanto, notificar_one () para ser chamado. Observe que isso só pode acontecer quando a primeira linha de cancelamento resultou na falha do thread.
Além de um problema de fome onde tantos threads estão chamando start () / stop () que a contagem nunca atinge -1000 e cancel () nunca retorna, o que se pode aceitar como "improvável e nunca durando muito", há outro bug:
É possível que haja um thread dentro da área 'Fazer coisas', digamos que ele esteja apenas chamando stop (); naquele momento, uma thread executa a primeira linha de cancel () lendo o valor 1 com fetch_sub (1000) e caindo. Mas antes de pegar o mutex e / ou fazer a chamada para esperar (lk), o primeiro thread executa a primeira linha de stop (), lê -999 e chama cv.notify_one ()!
Então esta chamada para notificar_one () é feita ANTES de estarmos esperando () - na variável de condição! E o programa travaria indefinidamente.
Por esta razão, não devemos ser capazes de chamar notificar_one () até que chamemos wait (). Observe que o poder de uma variável de condição reside no fato de que ela é capaz de desbloquear atomicamente o mutex, verificar se uma chamada para notificar_one () aconteceu e ir dormir ou não. Você não pode enganar, mas você fazer necessidade de manter o mutex bloqueado sempre que você fazer alterações em variáveis que podem mudar a condição de falso para verdadeiro e mantê -la trancada ao chamar notify_one () por causa de condições de corrida como descrito aqui.
Neste exemplo, entretanto, não há condição. Por que não usei como condição 'count == -1000'? Porque isso não é nada interessante aqui: assim que -1000 for atingido, temos certeza de que nenhum novo tópico entrará na área 'Fazer coisas'. Além disso, as threads ainda podem chamar start () e irão incrementar a contagem (para -999 e -998 etc), mas não nos importamos com isso. A única coisa que importa é que -1000 foi alcançado - para que possamos saber com certeza que não há mais tópicos na área 'Fazer coisas'. Temos certeza de que este é o caso quando notificar_one () está sendo chamado, mas como ter certeza de não chamar notificar_one () antes de cancelar () bloquear seu mutex? Apenas bloquear cancel_mutex antes de notificar_one () não vai ajudar, é claro.
O problema é que, apesar de não estarmos esperando por uma condição, ainda existe uma condição e precisamos bloquear o mutex
1) antes que essa condição seja alcançada 2) antes de chamar notifiquem_um.
O código correto, portanto, torna-se:
void stop()
{
if (count.fetch_sub(1) == -999)
{
cancel_mutex.lock();
cancel_mutex.unlock();
cv.notify_one();
}
}
[... mesmo começo () ...]
void cancel()
{
std::unique_lock<std::mutex> lk(cancel_mutex);
if (count.fetch_sub(1000) == 0)
return;
cancel_cv.wait(lk);
}
Claro que este é apenas um exemplo, mas outros casos são muito semelhantes; em quase todos os casos em que você usa uma variável condicional, você precisará ter aquele mutex bloqueado (em breve) antes de chamar not_one (), ou então é possível chamá-lo antes de chamar wait ().
Observe que eu desbloqueei o mutex antes de chamar o notificar_one () neste caso, porque caso contrário, há a (pequena) chance de que a chamada para o notificador_one () acorde o segmento esperando pela variável de condição que tentará pegar o mutex e bloco, antes de liberarmos o mutex novamente. Isso é apenas um pouco mais lento do que o necessário.
Este exemplo foi meio especial porque a linha que muda a condição é executada pela mesma thread que chama wait ().
Mais comum é o caso em que uma thread simplesmente espera que uma condição se torne verdadeira e outra thread obtém o bloqueio antes de alterar as variáveis envolvidas naquela condição (fazendo com que possivelmente se torne verdadeira). Nesse caso, o mutex é bloqueado imediatamente antes (e depois) da condição se tornar verdadeira - portanto, está totalmente ok apenas desbloquear o mutex antes de chamar o notificador _ * () nesse caso.
wait morphing
otimização) Regra prática explicada neste link: notificar com bloqueio é melhor em situações com mais de 2 threads para resultados mais previsíveis.