Ложный пробуждение допускается различными платформами. Чтобы противостоять этому, мы напишем ниже циклический механизм:
while(ContinueWaiting())
cv.wait(lock); // cv is a `std::conditional_variable` object
То же самое понятно для conditional_variable::wait_until()
,
Но посмотрите на приведенный ниже пример:
const auto duration = Returns_10_seconds();
while(!Predicate())
cv.wait_for(lock, duration);
Представьте, что ложное пробуждение произошло через 1 секунду. Тайм-аут еще не достигнут.
Будет ли это ждать еще 10 секунд? Это привело бы к бесконечному циклу, который, я уверен, не должен происходить. Из исходного кода, внутренне wait_for()
звонки wait_until()
,
Я хочу понять, как wait_for()
имеет дело с ложными пробуждениями?
Я хочу понять, как
wait_for()
имеет дело с ложными пробуждениями?
Это не так.
Эта функция обычно используется в ситуации, когда, если вы внезапно просыпаетесь, вы все равно хотите выполнить какую-то другую работу. И если вы не просыпаетесь внезапно, вы хотите вызвать «ложное» пробуждение к тому времени duration
прошел. Это означает, что он обычно не используется в цикле, как вы показываете, именно по указанным вами причинам. То есть таймауты и ложные пробуждения лечатся одинаково.
Теперь вам может быть интересно, что делает версия предиката, поскольку она подразумевает цикл?
template <class Rep, class Period, class Predicate>
bool
wait_for(unique_lock<mutex>& lock, const chrono::duration<Rep, Period>& rel_time,
Predicate pred);
Это указано, чтобы иметь такие же эффекты, как:
return wait_until(lock, chrono::steady_clock::now() + rel_time, std::move(pred));
wait_until
изменение делает различать ложные пробуждения и тайм-ауты. Это делается с помощью следующего цикла:
while (!pred())
if (wait_until(lock, abs_time) == cv_status::timeout)
return pred();
return true;
Вот что говорит стандарт о ложных пробуждениях:
30.5 Условные переменные [thread.condition]
Переменные условия предоставляют примитивы синхронизации, используемые для блокировки потока, пока не получат уведомление от некоторых других
Поток, что некоторое условие выполняется или пока не достигнуто системное время.…
10 Примечание. Пользователь должен убедиться, что ожидающие потоки
не допускайте ошибочного предположения, что поток завершен, если они
опыт ложных пробуждений.
Из формулировки кажется довольно ясным, что ответственность за работу с ложными пробуждениями лежит на пользователе.
const auto duration = Returns_10_seconds (); while (cv.wait_for (блокировка, длительность) == std :: cv_status :: timeout);
Это определенно неправильно, и поэтому не имеет смысла обсуждать, как это исправить в случае ложных пробуждений, поскольку это не работает даже в случае обычных пробуждений, поскольку условие ожидания не пересматривается после возвращения из ожидание.
const auto duration = Returns_10_seconds();
while(!Predicate())
cv.wait_for(lock, duration);
Даже после редактирования ответ остается прежним: вы не можете действительно справиться с «ложными пробуждениями», потому что вы не можете точно сказать причину пробуждения — это вполне может быть вполне законным пробуждением из-за вызова condition_variable::notifyXXX
до истечения времени ожидания.
Во-первых, обратите внимание, что вы не можете отличить пробуждение, вызванное вызовом condition_variable::notifyXXX
и пробуждение, вызванное, например, сигналом POSIX [1].
Во-вторых, даже если сигналы POSIX не имеют значения, ожидающий поток по-прежнему должен пересматривать условие, так как условие может изменяться между временем, когда переменная условия сигнализирует, и ожидающий поток возвращается из условия ожидания.
Что вам действительно нужно сделать, так это лечить особым образом, не просыпаться до истечения времени ожидания, а просыпаться из-за тайм-аута. И это полностью зависит от причин, по которым в первую очередь должен быть установлен тайм-аут, то есть от специфики области приложения / проблемы.
[1] если ожидание переменной условия прерывается сигналом, после выполнения обработчика сигнала потоку разрешается либо возобновить ожидание, либо вернутьсяПроверь дорогой позвони сам
cv.wait_for()
не имеет дело с ложными пробуждениями.
Вы можете бороться с ложными пробуждениями, передавая логический флаг потоку по ссылке и проверяя его, когда cv.wait_until()
это не тайм-аут
В этом случае, когда main()
поток не устанавливает terminate
а также cv.wait_until()
имеет no_timeout, это означает, что время ожидания не достигнуто, но cv уведомлено (системой), и поэтому это ложный вызов.
bool terminate = false;
std::unique_lock<std::mutex> lock(mutex);
const auto time_point = std::chrono::system_clock::now() + std::chrono::seconds(10);
const std::cv_status status = cv.wait_until(lock, time_point);
if (status == std::cv_status::timeout) {
std::cout << "timeout" << std::endl;
}
else { // no_timeout
if (terminate) {
std::cout << "terminate" << std::endl;
break;
}
else {
std::cout << "spurious" << std::endl;
}
}
Полный код
#include <iostream>
#include <mutex>
#include <condition_variable>
#include <chrono>
class Thread {
private:
std::condition_variable cv;
std::mutex mutex;
bool terminate = false;
std::thread thread;
public:
Thread() {
thread = std::thread([&]() {
while (true) {
std::unique_lock<std::mutex> lock(mutex);
const auto time_point = std::chrono::system_clock::now() + std::chrono::seconds(10);
const std::cv_status status = cv.wait_until(lock, time_point);
if (status == std::cv_status::timeout) {
std::cout << "timeout" << std::endl;
}
else { // no_timeout
if (terminate) {
std::cout << "terminate" << std::endl;
break;
}
else {
std::cout << "spurious" << std::endl;
}
}
}
});
}
virtual ~Thread() {
{
std::lock_guard<std::mutex> lock(mutex);
terminate = true;
}
cv.notify_all();
if (thread.joinable()) {
thread.join();
}
}
};
void main() {
{
Thread thread;
std::this_thread::sleep_for(std::chrono::seconds(15));
}
std::cin.get();
}
Полный код Результат, когда main () sleep_for ()
5 seconds:
terminate
15 seconds:
timeout
terminate
15 seconds in Visual Studio Debug mode:
spurious
terminate