С. Мейерс - Эффективный и современный C++ (1114942), страница 61
Текст из файла (страница 61)
Значение имеет нс фьючсрс, для которого вызывается g e t или w a i t , а совместно используемое состояние, на которое ссылается фьючерс. (В разделе 7.4 обсуждается взаимосвязь фьючерсов и совместно используемых состояний.) Поско11ьку s t d : : future поддерживаютперемещение, а также могут ис1юньзоваться для построения s t d : : s h a red_future и посколькуs t d : : shared future могут копироваться, объект фьючсрса, ссь111ающийся на совместно используемое состояние, возникающее из вызова s t d : : a s yn c , в который была передана f, вероятно,будет отличаться от фьючерса, возвращенного s t d : : a s ync.
Однако обычно нросто говорят о вызове фуню1ий-членов get или w a i t фьючсрса, возвращенного из s t d : : a s yn c ._250Глава 7. Параллельные вычисленияпоток - t, отсюда вытекает невозможность предсказать, будет ли f выполнятьсяпотоком, отличным от t .•посколькуможет оказаться невозможно гарантировать, что функции-члены get или wai t объекта fut будут вызваны на всех путях выполнения программы.Может быть невозможно предсказать, будет ли f выполнена вообще,Гибкость планирования стратегии запуска по умолчанию часто плохо комбинируетсяс использованием переменных thread_loca l, поскольку она означает, что если f читаетили записывает такую локальную память потока (thread-local storage - TLS), то невозможно предсказать, к переменным какого потока будет обращение:auto fut=std : : async ( f ) ; // TLS для fможет принадлежать11 независимому потоку, но может11 принадлежать и потоку, вызывающему// get или wait объекта futЭто также влияет на циклы на основе wa i t с использованием тайм-аутов, поскольку вызов wa it_for или wai t_unt i l для откладываемой задачи (см.
раздел 7. 1 ) дает значениеs t d : : future _ status : : de f e r red. Это означает, что приведенный далее цикл, которыйвыглядит как в конечном итоге завершающийся, может оказаться бесконечным:using namespace std : : literals; // Суффиксы длительности С++1 4 ;/ / см . раздел 6 . 41 1 f ждет 1 с, затем выполняется11 возврат из функцииvoid f ( ){std: : this thread : : s leep_for ( l s ) ;auto fut=std : : async ( f ) ;while ( fut . wait_for ( l OOms ) ! =// ( Концептуально ) асинхронное11 выполнение функции f/ / Цикл до завершения f .
. .std: : future status : : ready)_1 1 . . . которого никогда не будет !Если функция f выполняется параллельно с потоком, вызывающим std : : async (т.е.если выбранная для f стратегия запуска - std : : launch : : async ) , нет никаких проблем(в предположении, что f в конечном итоге завершится), но если выполнение f откладывается, fut . wai t for всегда будет возвращать s td : : future_status : : deferred.
Этовозвращаемое значение никогда не станет равным std : : future_status : : ready, так чтоцикл никогда не завершится.Такого рода ошибки легко упустить во время разработки и модульного тестирования,потому что они могут проявляться только при больших нагрузках. При этом возможнопревышение подписки или исчерпание потоков, и в такой ситуации задача, скорее всего,будет отложена. В конечном итоге, если аппаратному обеспечению не угрожает превышение подписки или исчерпание потоков, у системы времени выполнения нет никакихоснований для того, чтобы не запланировать параллельное выполнение задачи._7.2.Есnи важна асинхронность, указывайте std::launch::async251Исправить ошибку просто: следует проверить фьючерс, соответствующий вызовуstd : : async, и выяснить, не отложена ли данная задача.
Если отложена, то надо избегатьвхода в цикл на основе тайм-аутов. К сожалению, нет непосредственного способа выяснитьу фьючерса, отложена ли задача. Вместо этого вы должны вызывать функцию на основе таймаута, такую как wai t _ for. В этом случае вы в действительности не хотите ничего ожидать,а хотите просто проверить, не возвращает ли она значение std : : future_status : : deferred,так что можно вызывать эту функцию с нулевым временем ожидания:auto fut=std: : async ( f ) ; // Как и ранееif (fut . wait for (Os) =// Если задача отложена..._std: : future status : : deferred)_/ / ...используем wait или get// для синхронного вызова f/ / Если задача не отложена elsewhile ( fut . wait for ( l OOms ) ! =// бесконечный циклstd: : future_status : : ready) { // невозможен ( если f в11 конце концов завершается)11 Задача не отложена и не готова ,11 так что она выполняется параллельно11 fut выполненИз всего изложенного получается, что применение std : : а s упс со стратегией запускапо умолчанию отлично работает, пока выполняются следующие условия.•Задача не обязана работать параллельно с потоком, вызывающим get или wa it.•Не имеет значения, переменные t h r ea d_ loca l какого потока читаются или записываются.•Либо гарантируется вызов get или wai t для фьючерса, возвращаемого std : : async,либо ситуация, когда задача не выполняется совсем, является приемлемой.•Код с использованием wai t _for или wai t _uпt i l учитывает возможность отложенного состояния задачи.Если не выполняется любое из этих условий, то вы, вероятно, захотите гарантировать, что std : : a s ync обеспечит асинхронное выполнение задачи.
Для этого в качествепервого аргумента в нее надо передать значение std : : l aunch : : a s ync:auto fut=std : : async ( std: : launch : : async, f ) ; / / Асинхронный11 запуск fНа практике удобно иметь функцию, работающую как std : : async, но автоматическииспользующую стратегию запуска s t d : : l aunch : : a sync. Такую функцию несложно написать. Вот как выглядит версия этой функции для С++ 1 1 :252Гпава 7. Параппепьные вычиспенияtemplate<typename F, typename . . . Ts>inlinestd : : future<typename std : : result of<F ( Ts .
. . ) > : : type>reallyAsync ( F& & f, Ts&& . . . params )/ / Возврат фьючерса{/ / для асинхронногоreturn std: : async ( std : : launch : : async, // вызова f ( params . . . )std: : forward<F> ( f ) ,std: : forward<Ts> ( params ) . . . ) ;Эта функция получает вызываемый объект f и нуль или более параметров pa ramsи выполняет их прямую передачу (см. раздел 5.3) в std : : async, передавая также значение std : : launch : : async в качестве стратегии вызова. Подобно std : : async, она возвращает std : : future для результата выполнения f с параметрами params.
Определить типэтого результата просто, так как его нам дает свойство типа std : : resul t _o f. (Информацию о свойствах типов вы найдете в разделе 3.3.)Функция rea l lyAsync используется так же, как и std : : a s ync:auto fut=reallyAsync ( f ) ; / / Асинхронный запуск f; генерирует/ / исключение, если это делает11 std : : аsупсВ С++ 14 возможность вывода возвращаемого типа rea l l yAsync сокращает объявление функции:template<typename F, typename . .
. Ts>inl ine/ / С++14autoreallyAsync ( F& & f , Ts&& . . .{return std : : async ( std :std:std :params ): launch : : async,: forward<F> ( f ) ,: forward<Ts> ( params ) . . . ) ;Эта версия делает кристально ясным то, что rea l l yAsync не делает ничего, кроме вызоваstd : : async со стратегией запуска std : : l aunch : : async.Сnедует запомнить•Стратегия запуска по умолчанию для s t d : : a s ync допускает как асинхронное, таки синхронное выполнение задачи.•Эта гибкость ведет к неопределенности при обращении к переменным t h read_ loca l ,к тому, что задача может никогда не быть выполнена, и влияет на логику программыдля вызовов wa i t на основе тайм-аутов.•Если асинхронное выполнение задачи критично, указывайте стратегию запускаs t d : : l aunch : : a sync.7.2.Есл и важна асинхронность, указывайте std::launch::async2537.3. Деп айте s td : : thread неподкпючаемымна всех путях выпопненияКаждый объект s t d : : t h read может находиться в двух состояниях: подключаемом(joinaЬ\e) и неподключаемом (unjoinaЬle)'.
Подключаемый s t d : : t h read соответствуетасинхронному потоку выполнения, который выполняется или может выполняться. Например, таковым является s t d : : thread, соответствующий потоку, который заблокирован или ожидает запуска планировщиком. Объекты std : : thread, соответствующие потокам, которые выполняются до полного завершения, также рассматриваются как подключаемые.Неподключаемые объекты s t d : : t hread являются именно тем, что вы и ожидаете,а именно - объектами s t d : : thread, которые не являются подключаемыми.
Неподключаемые объекты std : : thread включают следующие.•Объекты s td : : thread, созданные конструкторами по умолчанию.Такиеstd : : thread не имеют выполняемой функции, а значит, не соответствуют никакомупотоку выполнения.•Объекты std: : thread, из которых выполнено перемещение.•Объекты std : : thread, для которых выполнена функция-член j oin. После выполнения функции-члена j oi n объект std : : thread больше не соответствует потокувыполнения, который при этом вызове полностью завершается.•Функциячлен detach разрывает связь между объектом s t d : : thread и соответствующим емупотоком выполнения.В результате перемещения поток выполнения, соответствующий std : : thread, становится соответствующим друтому объекту std : : thread.Объекты std : : thread, для которых выполнена функция-член detach.Одной из причин, по которым так важна подключаемость std : : thread, является то, чтопри вызове деструктора для подключаемого объекта программа завершает свою работу.