Энтони Уильямс - Параллельное программирование на С++ в действии. Практика разработки многопоточных программ
- Название:Параллельное программирование на С++ в действии. Практика разработки многопоточных программ
- Автор:
- Жанр:
- Издательство:ДМК Пресс
- Год:2012
- Город:Москва
- ISBN:978-5-94074-448-1
- Рейтинг:
- Избранное:Добавить в избранное
-
Отзывы:
-
Ваша оценка:
Энтони Уильямс - Параллельное программирование на С++ в действии. Практика разработки многопоточных программ краткое содержание
Книга «Параллельное программирование на С++ в действии» не предполагает предварительных знаний в этой области. Вдумчиво читая ее, вы научитесь писать надежные и элегантные многопоточные программы на С++11. Вы узнаете о том, что такое потоковая модель памяти, и о том, какие средства поддержки многопоточности, в том числе запуска и синхронизации потоков, имеются в стандартной библиотеке. Попутно вы познакомитесь с различными нетривиальными проблемами программирования в условиях параллелизма.
Параллельное программирование на С++ в действии. Практика разработки многопоточных программ - читать онлайн бесплатно полную версию (весь текст целиком)
Интервал:
Закладка:
Можно также вычесть один момент времени из другого при условии, что они относятся к одним и тем же часам. В результате получиться интервал между двумя моментами. Это полезно для хронометража участков программы, например:
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
#include
#include
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 lk(m);
while(!done) {
if (cv.wait_until(lk, timeout) == std::cv_status::timeout)
break;
}
return done;
}
Это рекомендуемый способ ожидания условной переменной с ограничением по времени в случае, когда предикат не указывается. При этом ограничивается общее время выполнения цикла. В разделе 4.1.1 мы видели, что при использовании условных переменных без предиката цикл необходим для защиты от ложных пробуждений. Но если вызывать в цикле wait_for()
, то может получиться, что функция прождёт почти все отведенное время, а затем произойдёт ложное пробуждение, после чего на следующей итерации отсчет времени начнется заново. И так может происходить сколько угодно раз, в результате чего общее время ожидания окажется неограниченным.
Вооружившись знаниями о том, как задавать таймауты, рассмотрим функции, в которых таймауты используются.
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 или std::shared_future |
wait_for( duration ) wait_until( time_point ) |
std::future_status::timeout , если истек таймаут, std::future_status::ready , если будущий результат готов, std::future_status::deferred , если в будущем результате хранится отложенная функция, которая еще не начала исполняться |
Теперь, когда мы рассмотрели условные переменные, будущие результаты, обещания и упакованные задачи, настало время представить более широкую картину их применения для синхронизации операций, выполняемых в разных потоках.
Читать дальшеИнтервал:
Закладка: