![]() |
![]() ![]() ![]() ![]() ![]() |
![]() |
Class execution_context (version 2)Boost , Chapter 1. Context , Chapter 1. Context
|
![]() |
Note |
---|---|
Этот класс включен по умолчанию. |
Классexecution_contextинкапсулирует переключение контекста и управляет стеком связанного контекста (выделение/выделение).
execution_contextвыделяет контекстный стек (используя его)StackAllocatorаргумент и создает поверх него управляющую структуру. Эта структура отвечает за управление стеком контекста. Адрес структуры управления хранится в первом стеке контекста (например, он не может быть напрямую доступен изexecution_context).. В отличие отexecution_context(v1)право собственности на структуру управления не разделено (никакой переменной члена для структуры управления вexecution_context).execution_contextсохраняет внутренне состояние, которое перемещается вызовомexecution_context::operator()(<*this
>будет признан недействительным), например, после вызоваexecution_context::operator(),<*this
>не может использоваться для дополнительного переключателя контекста.
execution_contextявляется только подвижно-конструктивным и подвижно-назначаемым.
Перемещенное состояние присваивается новому экземпляруexecution_context. Этот объект становится первым аргументом контекстной функции, если контекст был возобновлен в первый раз, или первым элементом в кортеже, возвращеннымexecution_context::operator(), который был вызван в возобновленном контексте. В отличие отexecution_context(v1), переключение контекста происходит быстрее, потому что не задействован глобальный указатель и т.д.
![]() |
Important |
---|---|
Сегментированные стеки не поддерживаютсяexecution_context(v2). |
По возвращении контекст-функция текущего контекста должна указатьexecution_context, на который передается контроль исполнения после прекращения текущего контекста.
Если экземпляр с действительным состоянием выходит из области действия, а функция контекста еще не вернулась, стек проходит для доступа к структуре управления (адрес, сохраненный в первом кадре стека), и стек контекста распределяется через.StackAllocator. Стековая ходьба делает разрушениеexecution_contextмедленным и должна быть предотвращена, если это возможно.
execution_contextожидаетконтекстную функциюс подписью<execution_context(execution_context
ctx,Args...args)
>. Параметр<ctx
>представляет контекст, из которого этот контекст был возобновлен (например, который назвалexecution_context::operator()on<*this
>) и<args
>представляют собой данные, переданныеexecution_context::operator(). Значение возврата представляет собой исполняемый_контекст, который должен быть возобновлен после терминации этого контекста.
Преимуществамиexecution_context(v2)по сравнению сexecution_context(v1)являются: более быстрый переключатель контекста, безопасность типа переданных/возвращенных аргументов.
int n=35; ctx::execution_context<int> source( [n](ctx::execution_context<int> sink, int) mutable { int a=0; int b=1; while(n-->0){ auto result=sink(a); sink=std::move(std::get<0>(result)); auto next=a+b; a=b; b=next; } return sink; }); for(int i=0;i<10;++i){ auto result=source(i); source=std::move(std::get<0>(result)); std::cout<<std::get<1>(result)<<" "; } output: 0 1 1 2 3 5 8 13 21 34
Этот простой пример демонстрирует базовое использованиеexecution_contextв качестве генератора. Контекст<sink
>представляет собойосновной-контекст (функцияосновной()работающий).<sink
>генерируется рамкой (первый элемент списка параметров лямбды). Поскольку состояние недействительно (== изменяется) каждым вызовомexecution_context::operator(), новое состояниеexecution_context, возвращаемоеexecution_context::operator(), должно быть назначено<sink
>после каждого вызова.
Ламбда, вычисляющая числа Фибоначчи, выполняется внутри контекста, представленного<source
>. Вычисленные числа Фибоначчи переносятся между двумя контекстами посредством выраженияsink(a)(и возвращаютсяsource()). Обратите внимание, что этот пример представляет собойгенератор, таким образом, значение, переданное в лямбду черезисточник(), не используется. Использованиебустера::optional<>в качестве перенесенного типа, также может быть уместным для выражения этого факта.
Локальные переменные<a
>,<b
>и<next
>остаются своими значениями в течение каждого контекстного переключателявыход (a). Это возможно, потому что<source
>имеет свой собственный стек, и стек обменивается каждым коммутатором контекста.
Если<execution_context<void>
>данные не передаются, выполняется только переключатель контекста.
boost::context::execution_context<void> ctx1([](boost::context::execution_context<void> ctx2){ std::printf("inside ctx1\n"); return ctx2(); }); ctx1(); output: inside ctx1
<ctx1()
>резюме<ctx1
>, например, лямбда, пропущенная у конструктора<ctx1
>, вводится. Аргумент<ctx2
>представляет собой контекст, который был приостановлен с призывом<ctx1()
>. Когда лямбда возвращается<ctx2
>, контекст<ctx1
>будет прекращен, а контекст, представленный<ctx2
>, возобновлен, следовательно, контроль исполнения возвращается из<ctx1()
>.
Аргументы, переданныеexecution_context::operator(), в одном контексте, передаются в качестве последних аргументовконтекст-функции, если контекст запущен впервые. Во всех следующих вызовахexecution_context::operator()аргументы, переданныеexecution_context::operator(), в одном контексте возвращаютсяexecution_context::operator()в другом контексте.
boost::context::execution_context<int> ctx1([](boost::context::execution_context<int> ctx2, int j){ std::printf("inside ctx1, j == %d\n", j); return ctx2(j+1); }); int i = 1; std::tie(ctx1, i) = ctx1(i); std::printf("i == %d\n", i); output: inside ctx1, j == 1 i == 2
<ctx1(i)
>входит в лямбду в контексте<ctx1
>с аргументом<j=1
>. Выражение<ctx2(j+1)
>возобновляет контекст, представленный<ctx2
>, и возвращает целое число<j+1
>. При возврате<ctx1(i)
>переменная<i
>содержит значение<j+1
>.
Если необходимо передать более одного аргумента, подпись контекстной функции просто расширяется.
boost::context::execution_context<int,int> ctx1([](boost::context::execution_context<int,int> ctx2, int i, int j){ std::printf("inside ctx1, i == %d j == %d\n", i, j); return ctx2(i+j,i-j); }); int i = 2, j = 1; std::tie(ctx1, i, j) = ctx1(i,j); std::printf("i == %d j == %d\n", i, j); output: inside ctx1, i == 2 j == 1 i == 3 j == 1
Для вариантов использования, которые требуют передачи данных различного типа в каждом направлении, можно использоватьboost::variant<>.
class X{ private: std::exception_ptr excptr_; boost::context::execution_context<boost::variant<int,std::string>> ctx_; public: X(): excptr_(), ctx_( [=](boost::context::execution_context<boost::variant<int,std::string>> ctx, boost::variant<int,std::string> data){ try { for (;;) { int i = boost::get<int>(data); data = boost::lexical_cast<std::string>(i); auto result = ctx( data); ctx = std::move( std::get<0>( result) ); data = std::get<1>( result); } catch (std::bad_cast const&) { excptr_=std::current_exception(); } return ctx; }) {} std::string operator()(int i){ boost::variant<int,std::string> data = i; auto result = ctx_( data); ctx_ = std::move( std::get<0>( result) ); data = std::get<1>( result); if(excptr_){ std::rethrow_exception(excptr_); } return boost::get<std::string>(data); } }; X x; std::cout << x( 7) << std::endl; output: 7
В случае однонаправленной передачи данных уместныбустер::optional<>или указатель.
Если функция, выполняемая внутриexecution_context, испускает исключение, приложение прекращается вызовомstd::terminate().std:: Exception_ptrможет использоваться для передачи исключений между различными контекстами исполнения.
![]() |
Important |
---|---|
Не прыгайте изнутри блок-уловки, а затем повторно бросайте исключение в другой контекст исполнения. |
Иногда полезно выполнять новую функцию поверх возобновленного контекста. Для этой целиexecution_context::operator()с первым аргументом<exec_ontop_arg
>следует использовать. Функция, принятая в качестве аргумента, должна возвращать набор из execution_context и аргументов.
boost::context::execution_context<int> f1(boost::context::execution_context<int> ctx,int data) { std::cout << "f1: entered first time: " << data << std::endl; std::tie(ctx,data) = ctx(data+1); std::cout << "f1: entered second time: " << data << std::endl; std::tie(ctx,data) = ctx(data+1); std::cout << "f1: entered third time: " << data << std::endl; return ctx; } std::tuple<boost::context::execution_context<int>,int> f2(boost::context::execution_context<int> ctx,int data) { std::cout << "f2: entered: " << data << std::endl; return std::make_tuple(std::move(ctx),-1); } int data = 0; ctx::execution_context< int > ctx(f1); std::tie(ctx,data) = ctx(data+1); std::cout << "f1: returned first time: " << data << std::endl; std::tie(ctx,data) = ctx(data+1); std::cout << "f1: returned second time: " << data << std::endl; std::tie(ctx,data) = ctx(ctx::exec_ontop_arg,f2,data+1); output: f1: entered first time: 1 f1: returned first time: 2 f1: entered second time: 3 f1: returned second time: 4 f2: entered: 5 f1: entered third time: -1
Выражение<ctx(ctx::exec_ontop_arg,f2,data+1)
>выполняет<f2()
>поверх контекста<ctx
>, например, дополнительная рамка стека выделяется поверх контекстного стека (перед<f1()
>).<f2()
>возвращает аргумент<-1
>, который будет возвращен вторым призывом<ctx(data+1)
>в<f1()
>.
Другой вариант — выполнить функцию поверх контекста, которая делает исключение.
struct interrupt { boost::context::execution_context< void > ctx; interrupt( boost::context::execution_context< void > && ctx_) : ctx( std::forward< boost::context::execution_context< void > >( ctx_) ) { } }; boost::context::execution_context<void> f1(boost::context::execution_context<void> ctx) { try { for (;;) { std::cout << "f1()" << std::endl; ctx = ctx(); } } catch (interrupt & e) { std::cout << "f1(): interrupted" << std::endl; ctx = std::move( e.ctx); } return ctx; } boost::context::execution_context<void> f2(boost::context::execution_context<void> ctx) { throw interrupt(std::move(ctx)); return ctx; } boost::context::execution_context< void > ctx(f1); ctx = ctx(); ctx = ctx(); ctx = ctx(boost::context::exec_ontop_arg,f2); output: f1() f1() f1(): interrupted
В этом примере<f2()
>используется для прерывания<for
>-петли в<f1()
>.
На конструкциюexecution_contextвыделен стек. Есликонтекстная функциявозвращается, стек будет уничтожен. Есликонтекстная функцияеще не вернулась и деструктор действительногоэкземпляра execution_context(например,execution_context::operator bool()возвращает<true
>) называется, стек также будет уничтожен.
![]() |
Important |
---|---|
Код, выполняемыйконтекстной функцией, не должен препятствовать распространениюдетали::forced_unwindисключения. Поглощение этого исключения приведет к провалу стека. Таким образом, любой код, который улавливает все исключения, должен повторно бросить любую ожидающуюдеталь::forced_unwindисключение. |
Выделение управляющих структур поверх стека требует выделенияstack_contextи создания управляющей структуры с размещением новой до созданияexecution_context.
![]() |
Note |
---|---|
Пользователь несет ответственность за разрушение структуры управления в верхней части стека. |
// stack-allocator used for (de-)allocating stack fixedsize_stack salloc( 4048); // allocate stack space stack_context sctx( salloc.allocate() ); // reserve space for control structure on top of the stack void * sp = static_cast< char * >( sctx.sp) - sizeof( my_control_structure); std::size_t size = sctx.size - sizeof( my_control_structure); // placement new creates control structure on reserved space my_control_structure * cs = new ( sp) my_control_structure( sp, size, sctx, salloc); ... // destructing the control structure cs->~my_control_structure(); ... struct my_control_structure { // captured context execution_context cctx; template< typename StackAllocator > my_control_structure( void * sp, std::size_t size, stack_context sctx, StackAllocator salloc) : // create captured context cctx( std::allocator_arg, preallocated( sp, size, sctx), salloc, entry_func) { } ... };
/* * grammar: * P ---> E '\0' * E ---> T {('+'|'-') T} * T ---> S {('*'|'/') S} * S ---> digit | '(' E ')' */ class Parser{ // implementation omitted; see examples directory }; std::istringstream is("1+1"); bool done=false; std::exception_ptr except; // execute parser in new execution context boost::context::execution_context<char> source( [&is,&done,&except](ctx::execution_context<char> sink,char){ // create parser with callback function Parser p( is, [&sink](char ch){ // resume main execution context auto result = sink(ch); sink = std::move(std::get<0>(result)); }); try { // start recursive parsing p.run(); } catch (...) { // store other exceptions in exception-pointer except = std::current_exception(); } // set termination flag done=true; // resume main execution context return sink; }); // user-code pulls parsed data from parser // invert control flow auto result = source('\0'); source = std::move(std::get<0>(result)); char c = std::get<1>(result); if ( except) { std::rethrow_exception(except); } while( ! done) { printf("Parsed: %c\n",c); std::tie(source,c) = source('\0'); if (except) { std::rethrow_exception(except); } } output: Parsed: 1 Parsed: + Parsed: 1
В этом примере рекурсивный парсер спуска использует обратный вызов, чтобы излучать недавно принятый символ. Используяexecution_context, поток управления может быть инвертирован, например, пользовательский код вытягивает парсированные символы из парсера - вместо этого его выталкивают из парсера (через обратный вызов).
Данные (характер) передаются между двумяexecution_context.
Если код, выполняемыйexecution_context, испускает исключение, приложение прекращается.std:: Exception_ptrможет использоваться для передачи исключений между различными контекстами исполнения.
Иногда необходимо раскручивать стек незавершенного контекста, чтобы уничтожить локальные переменные стека, чтобы они могли высвобождать выделенные ресурсы (паттерн RAII). Пользователь несет ответственность за эту задачу.
execution_context
struct exec_ontop_arg_t {}; const exec_ontop_arg_t exec_ontop_arg{}; template< typename ... Args > class execution_context { public: template< typename Fn, typename ... Params > execution_context( Fn && fn, Params && ... params); template< typename StackAlloc, typename Fn, typename ... Params > execution_context( std::allocator_arg_t, StackAlloc salloc, Fn && fn, Params && ... params); template< typename StackAlloc, typename Fn, typename ... Params > execution_context( std::allocator_arg_t, preallocated palloc, StackAlloc salloc, Fn && fn, Params && ... params); template< typename Fn, typename ... Params > execution_context( std::allocator_arg_t, segemented_stack, Fn && fn, Params && ... params) = delete; template< typename Fn, typename ... Params > execution_context( std::allocator_arg_t, preallocated palloc, segmented, Fn && fn, Params && ... params)= delete; ~execution_context(); execution_context( execution_context && other) noexcept; execution_context & operator=( execution_context && other) noexcept; execution_context( execution_context const& other) noexcept = delete; execution_context & operator=( execution_context const& other) noexcept = delete; explicit operator bool() const noexcept; bool operator!() const noexcept; std::tuple< execution_context, Args ... > operator()( Args ... args); template< typename Fn > std::tuple< execution_context, Args ... > operator()( exec_ontop_arg_t, Fn && fn, Args ... args); bool operator==( execution_context const& other) const noexcept; bool operator!=( execution_context const& other) const noexcept; bool operator<( execution_context const& other) const noexcept; bool operator>( execution_context const& other) const noexcept; bool operator<=( execution_context const& other) const noexcept; bool operator>=( execution_context const& other) const noexcept; template< typename charT, class traitsT > friend std::basic_ostream< charT, traitsT > & operator<<( std::basic_ostream< charT, traitsT > & os, execution_context const& other); };
template< typename Fn, typename ... Params > execution_context( Fn && fn, Params && ... params); template< typename StackAlloc, typename Fn, typename ... Params > execution_context( std::allocator_arg_t, StackAlloc salloc, Fn && fn, Params && ... params); template< typename StackAlloc, typename Fn, typename ... Params > execution_context( std::allocator_arg_t, preallocated palloc, StackAlloc salloc, Fn && fn, Params && ... params);
Создает новый контекст выполнения и подготавливает контекст для выполнения<fn
>.<fixedsize_stack
>используется в качестве распределителя стека по умолчанию (размер стека == фиксированный размер_стек::traits::default_size()). Конструктор с типом аргумента<preallocated
>, используется для создания пользовательских данных(например, дополнительных структур управления)поверх стека.
~execution_context();
Уничтожает связанный стек, если<*this
>является действительным контекстом, напримерexecution_context::operator bool()Returns<true
>.
Ничего.
execution_context( execution_context && other) noexcept;
Перемещает базовый рекорд захвата в<*this
>.
Ничего.
execution_context & operator=( execution_context && other) noexcept;
Переносит состояние<other
>на<*this
>с помощью семантики перемещения.
Ничего.
operator bool
()
explicit operator bool() const noexcept;
<true
>, если<*this
>указывает на запись захвата.
Ничего.
operator!
()
bool operator!() const noexcept;
<true
>, если<*this
>не указывает на запись захвата.
Ничего.
operator()
()
std::tuple< execution_context< Args ... >, Args ... > operator()( Args ... args); // member of generic execution_context template execution_context< void > operator()(); // member of execution_context< void >
Хранит внутри текущие контекстные данные (указатель стека, указатель команд и регистры процессора) текущего активного контекста и восстанавливает контекстные данные из<*this
>, что подразумевает переход к контексту<*this
>. Аргументы<...args
>передаются в текущий контекст, который должен быть возвращен последним призывом к<execution_context::operator()
>в той же нити.
Кортеж execution_context и возвращенные аргументы перешли к последнему вызову<execution_context::operator()
>, если таковой имеется, и execution_context, представляющий контекст, который был приостановлен.
Возвращенное исполнение_контекст указывает на прекращение приостановленного контекста (возвращение из контекстной функции) через<bool
operator()
>. Если возвращенное исполнение_контекст прекращено, никакие данные не передаются в возвращенном наборе.
operator()
()
template< typename Fn > std::tuple< execution_context< Args ... >, Args ... > operator()( exec_ontop_arg_t, Fn && fn, Args ... args); // member of generic execution_context template< typename Fn > execution_context< void > operator()( exec_ontop_arg_t, Fn && fn); // member of execution_context< void >
Аналогичноexecution_context::operator(). Кроме того, функция<fn
>выполняется в контексте<*this
>(например, кадр стека<fn
>выделен на стеке<*this
>).
Кортеж execution_context и возвращенные аргументы перешли к последнему вызову<execution_context::operator()
>, если таковой имеется, и execution_context, представляющий контекст, который был приостановлен.
Кортеж execution_context и возвращенные аргументы из<fn
>передаются в качестве аргументов в контекст-функцию возобновленного контекста (если контекст введен впервые) или эти аргументы возвращаются из<execution_context::operator()
>в возобновленном контексте.
Функция<fn
>требует возврата набора execution_context и аргументовсм. описание.
Контекст, называющий эту функцию, не должен быть уничтожен до того, как аргументы, которые будут возвращены из<fn
>, будут сохранены по крайней мере в стековой системе возобновленного контекста.
Возвращенное исполнение_контекст указывает на прекращение приостановленного контекста (возвращение из контекстной функции) через<bool
operator()
>. Если возвращенное исполнение_контекст прекращено, никакие данные не передаются в возвращенном наборе.
operator==
()
bool operator==( execution_context const& other) const noexcept;
<true
>, если<*this
>и<other
>представляют один и тот же контекст исполнения,<false
>в противном случае.
Ничего.
operator!=
()
bool operator!=( execution_context const& other) const noexcept;
<! (other == * this)
>
Ничего.
operator<
()
bool operator<( execution_context const& other) const noexcept;
<true
>, если<*this!=other
>истинно и заданный реализацией полный порядок значений<execution_context
>помещает<*this
>перед<other
>, ложно в противном случае.
Ничего.
operator>
()
bool operator>( execution_context const& other) const noexcept;
<other<
*this
>
Ничего.
operator<=
()
bool operator<=( execution_context const& other) const noexcept;
<!(other<
*this)
>
Ничего.
operator>=
()
bool operator>=( execution_context const& other) const noexcept;
<!(*
this<
other)
>
Ничего.
operator<<()
template< typename charT, class traitsT > std::basic_ostream< charT, traitsT > & operator<<( std::basic_ostream< charT, traitsT > & os, execution_context const& other);
Записывает представление<other
>для потоковой передачи<os
>.
<os
>
Статья Class execution_context (version 2) раздела Chapter 1. Context Chapter 1. Context может быть полезна для разработчиков на c++ и boost.
Материалы статей собраны из открытых источников, владелец сайта не претендует на авторство. Там где авторство установить не удалось, материал подаётся без имени автора. В случае если Вы считаете, что Ваши права нарушены, пожалуйста, свяжитесь с владельцем сайта.
:: Главная :: Chapter 1. Context ::
реклама |