С. Мейерс - Эффективный и современный C++ (1114942), страница 64
Текст из файла (страница 64)
:...e.r.s>!!'��e Вызываемая ·вызова ,функция(обыч но) функцияСуществование общего состояния имеет важное значение, потому что поведение деструктора фьючерса - тема этого раздела - определяется общим состоянием, связанным с этим фьючерсом. В частности, справедливо следующее.•Деструктор последнеrо фьючерса, ссылающеrося на общее состояние для неотложенной задачи, запущенной с помощью std : : async, блокируется до тех пор, показадача не будет завершена. По сути, деструктор такого фьючерса неявно выполняетj o i n для потока, асинхронно выполняющего задачу.•Деструкторы всех прочих фьючерсов просто уничтожают объект фьючерса.
Дляасинхронно выполняющихся задач это сродни неявному отключению базового потока с помощью вызова detach. Для отложенных задач, для которых данный фьючерс является последним, это означает, что отложенная задача никогда не будет выполнена.Эти правила выглядят сложнее, чем есть на самом деле. Мы имеем дело с простым"нормальным" поведением и одним исключением. Нормальное поведение заключаетсяв том, что деструктор фьючерса уничтожает объект фьючерса. Вот и все. Он ничего неподключает, ничего не отключает, он ничего не запускает. Он просто уничтожает члены-данные фьючерса. (Ну, на самом деле он делает еще одно дело - уменьшает значение счетчика ссылок общего состояния, которое управляется как ссылающимися на негофьючерсами, так и объектами std : : promi se вызываемой функции. Этот счетчик ссылокпозволяет библиотеке знать, когда можно уничтожать общее состояние.
Информациюпо счетчикам ссылок вы можете найти в разделе 4.2.)Исключение из этого нормального поведения осуществляется только для фьючерса,для которого выполняются все перечисленные далее условия.262•Он ссылается на общее состояние, созданное вызовом std : : async.•Стратегия запуска задачи - std : : l aunch : : a sync (см. раздел 7.2), либо потому, чтоона выбрана системой времени выполнения, либо потому, что была явно указанав вызове std : : async.•Фьючерс является последним фьючерсом, ссылающимся на общее состояние.
Это всегда справедливо для s t d : : fu t u re . Для s t d : : sha red_fu ture, еслипри уничтожении фьючерса на то же самое общее состояние ссылаются другиеstd : : shared_ future, поведение уничтожаемого фьючерса - нормальное (т.е. просто уничтожаются его члены-данные).Гnава 7 . Пара nnеn ьные вычисnенияТолько когда все эти условия выполнены, деструктор фьючерса демонстрирует особоеповедение, и это поведение состоит в блокировке до тех пор, пока не будет завершенаасинхронно выполняющаяся задача. С практической точки зрения это равносильно неявному вызову j o i n для потока с запущенной задачей, созданной с помощью std : : async.Часто приходится слышать, что это исключение из нормального поведения деструктора фьючерса резюмируется как "Фьючерс из std : : a s ync блокируется в своем деструкторе': В качестве первого приближения это так, но иногда нам надо что-то большее, чемпервое приближение. Теперь вы знаете правду во всей ее красе и славе.Ваше удивление может принять и иную форму.
Например, "Не понимаю, почему имеется особое правило для общих состояний для неотложенных задач, запущенных с помощью std : : a sync': Это разумный вопрос. Я моrу сказать, что Комитет по стандартизациихотел избежать проблем, связанных с неявным вызовом det ach (см. раздел 7.3), но нехотел одобрять такую радикальную стратегию, как обязательное завершение программы (как сделано для подключаемых s t d : : t hread; см.
тот же раздел 7.3), так что в качестве компромисса был принят неявный вызов j oi n . Это решение не без противоречий, и были серьезные предложения отказаться от этого поведения в С++ 14. В концеконцов никакие изменения сделаны не были, так что поведение деструкторов фьючерсовв С++ l l согласуется с таковым в С++ 14.API для фьючерсов не предлагает способа определения, ссылается ли фьючерс на общее состояние, возникающее из вызова std : : as ync, так что невозможно узнать, будет лизаблокирован некоторый объект фьючерса в своем деструкторе для ожидания завершения асинхронно выполняющейся задачи. Это имеет некоторые интересные последствия./ / Этот контейнер может блокироваться в деструкторе , поскольку11 один или несколько содержащихся в нем фьючерсов могут// ссьu�аться на общее состояние неотложенного задания,11 запущенного с помощью std: : аsупсs td : : vector<std: : future<void>> fut s ; // Cм .
s td : : future<void>11 в разделе 7 . 5class Widget1 1 Объекты Widget могут/ / блокироваться в ихpuЬlic :/ / деструкторахprivate :std : : shared future<douЫe> fut ;);Конечно, если у вас есть способ узнать, что данный фьючерс не удовлетворяет условиям, приводящим к специальному поведению деструктора (например, в силу логики программы), вы можете быть уверены, что этот фьючерс не приведет к блокировкедеструктора. Например, претендовать на особое поведение могут только общие состояния, получающиеся в результате вызовов s t d : : a s ync, но есть и иные способы создания этих общих состояний.
Один из них - использование s t d : : packaged_tas k. Объектs t d : : packaged t a s k подготавливает функцию (или иной вызываемый объект) к асинхронному выполнению, "заворачивая" ее таким образом, что ее результат помещается7 .4. Помните о ра э ном поведении деструкторов дескрипторов потоков263в общее состояние. Фьючерс, ссылающийся на это общее состояние, может быть полученс помощью функции get_future объекта s t d : : pa c kaged_t a s k.int calcValue ( ) ;std: : packaged task<int ( ) >pt ( calcValue ) ;auto fut = pt . get_future ( ) ;_/ / Выполняемая функция1 1 Заворачивание calcValue для1 1 асинхронного вьmолнения/ / Получение фьючерса для ptэтой точке мы знаем, что фьючерс fut не ссылается на общее состояние, созданноевызовом s t d : : a sync, так что его деструктор будет вести себя нормально.Будучи созданным, объект pt типа s t d : : packaged_t a s k может быть запущен в потоке.
(Он может быть запущен и с помощью вызова s t d : : a sync, но если вы хотите выполнить задачу с использованием std : : a s ync, то нет смысла создавать s t d : : pac kaged_ t a s k,поскольку std : : as ync делает все, что делает s t d : : packaged_t a s k до того, как планировщик начинает выполнение задачи.)Объекты s t d : : packaged_tas k не копируются, так что когда pt передается в конструктор s t d : : thread, он должен быть приведен к rvalue (с помощью s t d : : rnove; см.
раздел 5.1):Вstd : : thread t ( s td : : rnove ( pt ) ) ;1 1 Вьmолнение pt потоком tЭтот пример дает некоторое представление о нормальном поведении деструкторовфьючерсов, но его легче увидеть, если собрать весь код вместе в одном блоке:1 1 Начало блокаstd: : packaged_task<int ( ) >pt ( ca lcValue ) ;auto fut = pt . get_future ( ) ;std : : thread t ( std: : rnove (pt ) ) ;11 См . ниже1 1 Конец блокаНаиболее интересный код скрывается за троеточием " ... ", следующим за созданиемобъекта t типа s t d : : t hread и предшествующим концу блока. Имеются три основныевозможности.С t ничего не происходит.
В этом случае t в конце области видимости будет непод•ключаемым. Это приведет к завершению программы (см. раздел 7.3).•Для t вызывается функция-член j oin. В•Для t вызывается функция-член detach. В этомэтом случае фьючерсу fut не требуетсяблокировать деструктор, так как вызов j o i n уже имеется в вызывающем коде.случае фьючерсу fut не требуетсявызывать detach в деструкторе, поскольку вызывающий код уже сделал это.Другими словами, когда у вас есть фьючерс, соответствующий общему состоянию,получившемуся из-за применения s t d : : pa ckaged_t a s k, обычно не требуется приниматьспециальную стратегию деструкции, так как решение о прекращении выполнения программы, подключении или отключении потока принимается в коде, работающем с потоком s t d : : t hread, в котором выполняется s t d : : packaged_t as k.264Глава 7.
Параллельные вычисленияСледует за помнить•Деструкторы фьючерсов обычно просто уничтожают данные-члены ф ьючерсов.•Последний фьючерс, ссылающийся на общее состояние неотложенной задачи, запущенной с помощью std : : a sync, блокируется до завершения этой задачи.7 S Применяйте фьючерсы voidдnя одноразовы х сообщений о событиях..Иногда требуется, чтобы одна задача могла сообщить друтой, выполняющейся асинхронно, о том, что произошло некоторое событие, поскольку вторая задача не можетпродолжать работу, пока это событие не произойдет. Например, пока не будет инициализирована структура данных, не будет завершено некоторое вычисление или не будетобнаружен сигнал от датчика.