Код пропускает уведомление переменной условия, потому что:
- Мьютекс не удерживается в течение
stop_ = true
(это должно быть true
, а не false
).stop_
необходимо прочитать и изменить, пока удерживается мьютекс, и он не должен быть атомарным.Это частая причина состояний гонки, когда люди используют атомики вместе с мьютексами и переменными условия. - Код ожидания переменной условия не проверяет условие перед ожиданием.
Исправления:
class B;
class C
{
public:
C(const std::weak_ptr<B>& b) : mB(b) {}
~C() { stop(); }
void run()
{
while (true) {
std::unique_lock<std::mutex> uLock(mMutex);
while(!mStop /* && !other_real_condition */)
mCondition.wait_for(uLock, std::chrono::seconds(300));
if(mStop)
return;
// other_real_condition is true, process it.
}
}
void start()
{
mThread = std::thread(&C::run, this);
}
void stop()
{
{
std::unique_lock<std::mutex> uLock(mMutex);
mStop = true;
}
mCondition.notify_all();
if (mThread.joinable())
mThread.join();
}
private:
bool mStop = false; // <--- do not forget to initialize
std::condition_variable mCondition;
std::mutex mMutex;
std::thread mThread;
std::weak_ptr<B> mB;
};
class B : public std::enable_shared_from_this<B>
{
public:
// basic methods
void init()
{
mC = std::unique_ptr<C>(new C(shared_from_this()));
mC->start();
}
private:
std::unique_ptr<C> mC;
};
Если вы установили mStop
без удержания мьютекса, произойдет следующее:
| Thread 1 | Thread 2 |
| mStop = true | |
| mCondition.notify_all | |
| | mMutex.lock |
| | mCondition.wait_for |
В приведенном выше обсуждении поток 2 теряет уведомление и ожидает, хотя mStop
было установлено.
Блокировка мьютекса при обновлении общего состояния исправляет это состояние гонки:
| Thread 1 | Thread 2 |
| mMutex.lock | |
| mStop = true | |
| mCondition.notify_all | |
| mMutex.unlock | |
| | mMutex.lock |
| | mStop == true, no wait |
При ожидании переменных состояния общее состояние должно быть изменено и прочитано, пока удерживается блокировка мьютекса, в противном случае уведомления об условияхпотеряться, и это может привести к тупику (при ожидании без таймаута).Вот почему использование атомики вместе с мьютексами и условными переменными не нужно, вы либо используете атомики или мьютексы и условные переменные, но не оба.