Но и функции-члены объекта std::shared_future
не синхронизированы, поэтому во избежание гонки за данными при доступе к одному объекту из нескольких потоков вы сами должны обеспечить защиту. Но более предпочтительный способ — скопировать объект, так чтобы каждый поток работал со своей копией. Доступ к разделяемому асинхронному состоянию из нескольких потоков безопасен, если каждый поток обращается к этому состоянию через свой собственный объект std::shared_future
. См. Рис. 4.1.
Рис. 4.1. Использование нескольких объектов std::shared_future
, чтобы избежать гонки за данными
Одно из потенциальных применений std::shared_future
— реализация параллельных вычислений наподобие применяемых в сложных электронных таблицах: у каждой ячейки имеется единственное окончательное значение, на которое могут ссылаться формулы, хранящиеся в нескольких других ячейках. Формулы для вычисления значений в зависимых ячейках могут использовать std::shared_future
для ссылки на первую ячейку. Если формулы во всех ячейках вычисляются параллельно, то задачи, которые могут дойти до конца, дойдут, а те, что зависят от результатов вычислений других ячеек, окажутся заблокированы до разрешения зависимостей. Таким образом, система сможет но максимуму задействовать доступный аппаратный параллелизм.
Экземпляры std::shared_future
, ссылающиеся на некоторое асинхронное состояние, конструируются из экземпляров std::future
, ссылающихся на то же состояние. Поскольку объект std::future
не разделяет владение асинхронным состоянием ни с каким другим объектом, то передавать владение объекту std::shared_future
необходимо с помощью std::move
, что оставляет std::future
с пустым состоянием, как если бы он был сконструирован по умолчанию:
std::promise
std::future
(1) Будущий результат f
assert(f.valid());
действителен
std::shared_future
assert(!f.valid());←
(2) f больше не действителен
assert(sf.valid());←
(3) sf теперь действителен
Здесь будущий результат f
в начальный момент действителен (1)
, потому что ссылается на асинхронное состояние обещания p
, но после передачи состояния объекту sf
результат f
оказывается недействительным (2), a sf
— действительным (3).
Как и для других перемещаемых объектов, передача владения для r-значения производится неявно, поэтому объект std::shared_future
можно сконструировать прямо из значения, возвращаемого функцией-членом get_future()
объекта std::promise
, например:
std::promise
(1) Неявная передача владения
std::shared_future
Здесь передача владения неявная; объект std::shared_future<>
конструируется из r-значения типа std::future
(1).
У шаблона std::future
есть еще одна особенность, которая упрощает использование std::shared_future
совместно с новым механизмом автоматического выведения типа переменной из ее инициализатора (см. приложение А, раздел А.6). В шаблоне std::future
имеется функция-член share()
, которая создает новый объект std::shared_future
и сразу передаёт ему владение. Это позволяет сделать код короче и проще для изменения:
std::promise<
std::map
SomeAllocator>::iterator> p;
auto sf = p.get_future().share();
В данном случае для sf
выводится тип std::shared_future
, такое название произнести-то трудно. Если компаратор или распределитель изменятся, то вам нужно будет поменять лишь тип обещания, а тип будущего результата изменится автоматически.
Иногда требуется ограничить время ожидания события — либо потому что на время исполнения некоторого участка кода наложены жесткие ограничения, либо потому что поток может заняться другой полезной работой, если событие долго не возникает. Для этого во многих функциях ожидания имеются перегруженные варианты, позволяющие задать величину таймаута.
4.3. Ожидание с ограничением по времени
Все блокирующие вызовы, рассмотренные до сих пор, приостанавливали выполнение потока на неопределенно долгое время — до тех пор, пока не произойдёт ожидаемое событие. Часто это вполне приемлемого в некоторых случаях время ожидания желательно ограничить. Например, это может быть полезно, когда нужно отправить сообщение вида «Я еще жив» интерактивному пользователю или другому процессу или когда ожидание действительно необходимо прервать, потому что пользователь устал ждать и нажал Cancel.