4.3.4. Функции, принимающие таймаут
Простейший случай использования таймаута — задание паузы в потоке, чтобы он не отнимал у других потоков время, когда ему нечего делать. Соответствующий пример был приведён в разделе 4.1, где мы в цикле опрашивали флаг «done». Для этого использовались функции std::this_thread::sleep_for() и std::this_thread::sleep_until(). Обе работают как будильник: поток засыпает либо на указанный интервал (в случае sleep_for()), либо до указанного момента времени (в случае sleep_until()). Функцию sleep_for() имеет смысл применять в ситуации, описанной в разделе 4.1, когда что-то необходимо делать периодически и важна лишь продолжительность периода. С другой стороны, функция sleep_until() позволяет запланировать пробуждение потока в конкретный момент времени, например: запустить в полночь резервное копирование, начать в 6 утра распечатку платёжной ведомости или приостановить поток до момента следующего обновления кадра при воспроизведении видео.
Разумеется, таймаут принимают не только функции типа sleep. Выше мы видели, что таймаут можно задавать при ожидании условных переменных и будущих результатов. А также при попытке захватить мьютекс, если сам мьютекс такую возможность поддерживает. Обычные классы std::mutex и std::recursive_mutex не поддерживают таймаут при захвате, зато его поддерживают классы std::timed_mutex и std::recursive_timed_mutex. В том и в другом имеются функции-члены try_lock_for() и try_lock_until(), которые пытаются получить блокировку в течение указанного интервала или до наступления указанного момента времени. В табл. 4.1 перечислены функции из стандартной библиотеки С++, которые принимают таймауты, их параметры и возвращаемые значения. Параметр duration должен быть объектом типа std::duration<>, а параметр time_point — объектом типа std::time_point<>.
Таблица 4.1. Функции, принимающие таймаут
Класс / пространство имен Функции Возвращаемые значения std::this_thread пространство имен sleep_for(duration) sleep_until(time_point) Неприменимо std::condition_variable или std::condition_variable_any wait_for(lock, duration) wait_until(lock, time_point) std::cv_status::timeout или std::cv_status::no_timeout wait_for(lock, duration, predicate) wait_until(lock, time_point, predicate) bool — значение, возвращенное предикатом predicate при пробуждении std::timed_mutex или std::recursive_timed_mutex try_lock_for(duration) try_lock_until(time_point) bool — true, если мьютекс захвачен, иначе false std::unique_lock<TimedLockable> unique_lock(lockable, duration) unique_lock(lockable, time_point) Неприменимо — функция owns_lock() для вновь сконструированного объекта возвращает true, если мьютекс захвачен, иначе false try_lock_for(duration) try_lock_until(time_point) bool — true, если мьютекс захвачен, иначе false std::future<ValueType> или std::shared_future<ValueType> wait_for(duration) wait_until(time_point) std::future_status::timeout, если истек таймаут, std::future_status::ready, если будущий результат готов, std::future_status::deferred, если в будущем результате хранится отложенная функция, которая еще не начала исполнятьсяТеперь, когда мы рассмотрели условные переменные, будущие результаты, обещания и упакованные задачи, настало время представить более широкую картину их применения для синхронизации операций, выполняемых в разных потоках.