Я пытаюсь понять разницу между ложным и потерянным пробуждением в случае условной переменной. Ниже приведен небольшой фрагмент кода, который я пробовал. Я понимаю, что «потребитель» в этом случае может проснуться без какого-либо уведомления, и поэтому ожидание должно проверять наличие предиката.
Но как ожидание с предикатом решает проблему «потерянного пробуждения»? Как вы можете видеть в коде ниже; «подождать» не вызывается в течение 5 секунд, и я ожидал, что он пропустит первые несколько уведомлений; но с predate он ничего не пропускает. Сохраняются ли эти уведомления для будущего ожидания?
#include <iostream>
#include <deque>
#include <condition_variable>
#include <thread>
std::deque<int> q;
std::mutex m;
std::condition_variable cv;
void dump_q()
{
for (auto x: q) {
std::cout << x << std::endl;
}
}
void producer()
{
for(int i = 0; i < 10; i++) {
std::unique_lock<std::mutex> locker(m);
q.push_back(i);
std::cout << "produced: " << i << std::endl;
cv.notify_one();
std::this_thread::sleep_for(std::chrono::seconds(1));
locker.unlock();
}
}
void consumer()
{
while (true) {
int data = 0;
std::this_thread::sleep_for(std::chrono::seconds(5)); // <- should miss first 5 notications?
std::unique_lock<std::mutex> locker(m);
cv.wait(locker);
//cv.wait(locker, [](){return !q.empty();}); // <- this fixes both spurious and lost wakeups
data = q.front();
q.pop_front();
std::cout << "--> consumed: " << data << std::endl;
locker.unlock();
}
}
int main(int argc, char *argv[])
{
std::thread t1(producer);
std::thread t2(consumer);
t1.join();
t2.join();
return 0;
}
Это атомарная операция «разблокировать и ждать», которая предотвращает потерянные пробуждения. Потерянное пробуждение происходит следующим образом:
Вы можете увидеть риск потери пробуждения здесь. Между шагами 3 и 4 другой поток может получить блокировку и отправить пробуждение. Мы сняли блокировку, поэтому это может сделать другой поток, но мы еще не ждем, поэтому не получим сигнал.
Пока шаг 2 выполняется под защитой блокировки, а шаги 3 и 4 являются атомарными, риск потери пробуждения отсутствует. Пробуждение не может быть отправлено до тех пор, пока данные не будут изменены, что невозможно сделать, пока другой поток не получит блокировку. Поскольку 3 и 4 являются атомарными, любой поток, который видит блокировку как разблокированную, обязательно также увидит нас в ожидании.
Эта атомарная «разблокировка и ожидание» является основной целью условных переменных и причиной, по которой они всегда должны быть связаны с мьютексом и предикатом.
In code above, consumer is not waiting for first few notifications because it is sleeping. Is it not missing notify in this case? Is this case not similar to race condition between #3 and #4?
Неа. Не может быть.
Либо потребитель, который не ждет, удерживает блокировку, либо нет. Если потребитель, который не ждет, держит блокировку, он ничего не пропустит. Предикат не может измениться, когда он удерживает блокировку.
Если потребитель не держит блокировку, то не имеет значения, что он упускает. Когда он проверяет, должен ли он блокироваться на шаге 2, если он что-то пропустил, он обязательно увидит это на шаге 2 и увидит, что ему не нужно ждать, поэтому он не будет ждать пробуждения, которое он пропустил.
Таким образом, если предикат таков, что потоку не нужно ждать, поток не будет ждать, потому что он проверяет предикат. Нет возможности пропустить пробуждение до шага 1.
Единственное время, когда действительное пробуждение необходимо, это когда поток переходит в спящий режим. Атомарная разблокировка и спящий режим гарантируют, что поток может принять решение о переходе в спящий режим только тогда, когда он удерживает блокировку, а то, чего ему нужно ждать, еще не произошло.
@spa Я обновлю свой ответ. В этом случае шаг 2 невозможен, поэтому мы никогда не дойдем до шага 4.
@DavidSchwartz, извините, что задал этот вопрос поздно. Я все еще не уверен в одном. Вы говорите, что потребитель наверняка пропустит уведомления (из-за сна, std::this_thread::sleep_for(std::chrono::seconds(5));
)? Но поскольку потребитель проверяет предикат, не имеет значения, пропустит ли он их?
@Пробужденный Верно. Неважно, пропустит ли он пробуждение, если он все равно не ждал пробуждения в это время.
В приведенном выше коде потребитель не ждет первых нескольких уведомлений, потому что он находится в спящем режиме. В этом случае не пропало уведомление? Разве этот случай не похож на состояние гонки между № 3 и № 4? Спасибо за помощь.