4.3.3. Моменты времени
Момент времени представляется конкретизацией шаблона класса std::chrono::time_point<>, в первом параметре которой задаются используемые часы, а во втором — единица измерения (специализация шаблона std::chrono::duration<>). Значением момента времени является промежуток времени (измеряемый в указанных единицах) с некоторой конкретной точки на временной оси, которая называется эпохой часов. Эпоха часов — это основополагающее свойство, однако напрямую его запросить нельзя, и в стандарте С++ оно не определено. Из типичных эпох можно назвать полночь (00:00) 1 января 1970 года и момент, когда в последний раз был загружен компьютер, на котором исполняется приложение. У разных часов может быть общая или независимые эпохи. Если у двух часов общая эпоха, то псевдоним типа typedef time_point в одном классе может ссылаться на другой класс как на тип, ассоциированный с time_point. Хотя узнать, чему равна эпоха, невозможно, вы можете получить время между данным моментом time_point и эпохой с помощью функции-члена time_since_epoch(), которая возвращает интервал.
Например, можно задать момент времени std::chrono::time_point <std::chrono::system_clock, std::chrono::minutes>. Он представляет время по системным часам, выраженное в минутах, а не в естественных для этих часов единицах (как правило, секунды или доли секунды).
К объекту std::chrono::time_point<> можно прибавить интервал или вычесть из него интервал — в результате получится новый момент времени. Например, std::chrono::high_resolution_clock::now() + std::chrono::nanoseconds(500) соответствует моменту времени в будущем, который отстоит от текущего момента на 500 наносекунд. Это удобно для вычисления абсолютного таймаута, когда известна максимально допустимая продолжительность выполнения некоторого участка программы, и внутри этого участка есть несколько обращений к функциям с ожиданием или обращения к функциям, которые ничего не ждут, но предшествуют функции с ожиданием и занимают часть отведенного времени.
Можно также вычесть один момент времени из другого при условии, что они относятся к одним и тем же часам. В результате получиться интервал между двумя моментами. Это полезно для хронометража участков программы, например:
auto start = std::chrono::high_resolution_clock::now();
do_something();
auto stop = std::chrono::high_resolution_clock::now();
std::cout << "do_something() заняла "
<< std::chrono::duration<
double, std::chrono::seconds>(stop-start).count()
<< " секунд" << std::endl;
Однако параметр clock объекта std::chrono::time_point<> не только определяет эпоху. Если передать момент времени функции с ожиданием, принимающей абсолютный таймаут, то указанный в нем параметр clock используется для измерения времени. Это существенно в случае, когда часы подводятся, потому что механизм ожидания замечает, что наказания часов изменились, и не дает функции вернуть управление, пока функция-член часов now() не вернет значение, большее, чем задано в таймауте. Если часы подведены вперёд, то это может уменьшить общее время ожидания (измеренное но стабильным часам), а если назад — то увеличить.
Как и следовало ожидать, моменты времени применяются в вариантах функций с ожиданием, имена которых заканчиваются словом _until. Как правило, таймаут задается в виде смещения от значения some-clock::now(), вычисленного в определенной точке программы, хотя моменты времени, ассоциированные с системными часами, можно получить из time_t с помощью статической функции-члена std::chrono::system_clock::to_time_point(), если при планировании операций требуется использовать время в понятном пользователю масштабе. Например, если на ожидание события, связанного с условной переменной, отведено не более 500 мс, то можно написать такой код.
Листинг 4.11. Ожидание условной переменной с таймаутом
#include <condition_variable>
#include <mutex>
#include <chrono>
std::condition_variable cv;
bool done;
std::mutex m;
bool wait_loop() {
auto const timeout = std::chrono::steady_clock::now() +
std::chrono::milliseconds(500);
std::unique_lock<std::mutex> lk(m);
while(!done) {
if (cv.wait_until(lk, timeout) == std::cv_status::timeout)
break;
}
return done;
}
Это рекомендуемый способ ожидания условной переменной с ограничением по времени в случае, когда предикат не указывается. При этом ограничивается общее время выполнения цикла. В разделе 4.1.1 мы видели, что при использовании условных переменных без предиката цикл необходим для защиты от ложных пробуждений. Но если вызывать в цикле wait_for(), то может получиться, что функция прождёт почти все отведенное время, а затем произойдёт ложное пробуждение, после чего на следующей итерации отсчет времени начнется заново. И так может происходить сколько угодно раз, в результате чего общее время ожидания окажется неограниченным.
Вооружившись знаниями о том, как задавать таймауты, рассмотрим функции, в которых таймауты используются.