Home | Libraries | People | FAQ | More |
Each instance of coroutine represents a context (CPU registers and stack space) of execution or not-a-coroutine. Objects of type coroutine are moveable but not copyable and can be returned by a function.
boost::coro::coroutine< void() > make_coroutine(); void f() { boost::coro::coroutine< void() > c( make_coroutine() ); c(); }
Note | |
---|---|
Boost.Move is used to emulate rvalue references. |
Warning | |
---|---|
If coroutine is used in a multithreaded application, it can migrated between threads, but must not reference thread-local storage. |
Note | |
---|---|
If fiber-local storage is used on Windows, the user is responsible for calling ::FlsAlloc(), ::FlsFree(). |
A new coroutine is created from a callable object (known as the coroutine-function). The stack size, stack unwinding and floating-point preserving behavior are determined by additional arguments.
The coroutine constructor uses the StackAllocator concept from Boost.Context to allocate an associated stack, and the destructor uses the same StackAllocator concept to deallocate the stack. The default StackAllocator concept is StackAllocator, but a custom stack-allocator can be passed to the constructor (see documentation of Boost.Context).
The first argument of coroutine-function must be of type coroutine<>::self_t, used for yielding the active coroutine.
typedef boost::coro::coroutine< void(int) > coro_t; void f( coro_t::self_t & self, int i) { int x; ... self.yield( x); ... } coro_t c( boost::bind( f, _1) ); c( 7);
The coroutine-function, as well as its arguments, if any, are copied into the coroutine's state. If a reference is required, use boost::ref.
Note | |
---|---|
The maximum number of arguments of coroutine-function is 10. |
The first invocation of coroutine<>::operator()() invokes the coroutine-function in a newly created coroutine complete with registers, flags, stack and instruction pointers. When control should be returned to the original calling coroutine, call coroutine<>::self_t::yield() or coroutine<>::self_t::yield_break(). The current coroutine information (registers, flags, and stack and instruction pointers) is saved and the original coroutine information is restored. Calling coroutine<>::operator()() resumes execution in the second coroutine after saving the new state of the original coroutine.
typedef boost::coro::coroutine< void() > coro_t; void fn( coro_t::self_t & self, int j) { for( int i = 0; i < j; ++i) { std::cout << "fn(): local variable i == " << i << std::endl; // save current coroutine // value of local variable is preserved // transfer execution control back to main() self.yield(); // coroutine<>::operator()() was called // execution control transfered back from main() } } int main( int argc, char * argv[]) { coro_t c( boost::bind( fn, _1, 7) ); std::cout << "main() starts coroutine c" << std::endl; // yield was called so we returned while ( ! ctx.is_complete() ) { std::cout << "main() calls coroutine c" << std::endl; // execution control is transfered to c c(); // yield() was called within fn() } std::cout << "Done" << std::endl; return EXIT_SUCCESS; } output: main() starts coroutine c fn(): local variable i == 0 main() calls coroutine c fn(): local variable i == 1 main() calls coroutine c fn(): local variable i == 2 main() calls coroutine c fn(): local variable i == 3 main() calls coroutine c fn(): local variable i == 4 main() calls coroutine c fn(): local variable i == 5 main() calls coroutine c fn(): local variable i == 6 main() calls coroutine c Done
Warning | |
---|---|
Calling coroutine<>::operator()() from inside the same coroutine results in undefined behaviour. |
Note | |
---|---|
In contrast to threads, which are preemtive, coroutine switches are cooperative (programmer controls when switch will happen). The kernel is not involved in the coroutine switches. |
The first template argument of coroutine, Signature, defines the signature of the coroutine-function and its return type.
Note | |
---|---|
coroutine<>::self_t is not part of Signature and is allways expected to be the first argument of coroutine-function. |
coroutine<>::operator()() accepts arguments as defined in Signature and has the same return type. The arguments passed to coroutine<>::operator()(), in one coroutine, is returned by coroutine<>::self_t::yield() in the other coroutine or, if it is the first call to coroutine<>::operator()() (coroutine was not started), the coroutine-function will be entered and the arguments are passed to coroutine-function on entry.
The value given to coroutine<>::self_t::yield(), in one coroutine, is returned by coroutine<>::operator()() in the other coroutine.
typedef boost::coro::coroutine< int( int) > coro_t; int fn( coro_t::self_t & self, int i) { std::cout << "fn(): local variable i == " << i << std::endl; // save current coroutine context // transfer execution control back to caller // pass content of variable back int j = self.yield( i); // j == 10 because c( 10) in main() std::cout << "fn(): local variable j == " << j << std::endl; return j; } int main( int argc, char * argv[]) { coro_t c( boost::bind( fn, _1, _2) ); std::cout << "main(): call coroutine c" << std::endl; int x = c( 7); std::cout << "main(): transfered value: " << x << std::endl; x = c( 10); std::cout << "main(): transfered value: " << x << std::endl; std::cout << "Done" << std::endl; return EXIT_SUCCESS; } output: main(): call coroutine c fn(): local variable i == 7 main(): transfered value: 7 fn(): local variable j == 10 main(): transfered value: 10 Done
If coroutine-function has more than one argument coroutine<>::operator()() has the same size of arguments and coroutine<>::self_t::yield() returns a boost::tuple coresponding to Signature.
typedef boost::coro::coroutine< int(int,int) > coro_t; int fn( coro_t::self_t & self, int a, int b) { int tmp = a + b; boost::tuple< int, int > ret = self.yield( tmp); return ret.get< 0 >() + ret.get< 1 >(); } int main( int argc, char * argv[]) { coro_t coro( boost::bind( fn, _1, _2, _3) ); std::cout << "main(): call coroutine c" << std::endl; int res = coro( 3, 7); std::cout << "main(): 3 + 7 == " << res << std::endl; res = coro( 5, 7); std::cout << "main(): 5 + 7 == " << res << std::endl; std::cout << "Done" << std::endl; return EXIT_SUCCESS; } output: main(): call coroutine c main(): 3 + 7 == 10 main(): 5 + 7 == 12 Done
coroutine<>::self_t::yield_break() does not take arguments and returns nothing, it leaved the current coroutine and jumps back to the calling coroutine throwing the exception coroutine_terminated.
typedef boost::coro::coroutine< int(int,int) > coro_t; int fn( coro_t::self_t & self, int a, int b) { int tmp = a + b; boost::tuple< int, int > ret = self.yield( tmp); self.yield_return(); return -1; } int main( int argc, char * argv[]) { try { coro_t coro( boost::bind( fn, _1, _2, _3) ); std::cout << "main(): call coroutine c" << std::endl; int res = coro( 3, 7); std::cout << "main(): 3 + 7 == " << res << std::endl; res = coro( 5, 7); std::cout << "main(): 5 + 7 == " << res << std::endl; std::cout << "Done" << std::endl; return EXIT_SUCCESS; } catch ( boost::coro::coroutine_terminated const&) { std::cerr << "coroutine terminated" << std::endl; } return EXIT_FAILURE; } output: main(): call coroutine c main(): 3 + 7 == 10 coroutine terminated
A excepton thrown by coroutine-function is transfered by exception-pointer (see documentation of Boost.Exception for details and requirements) and will be re-thrown by coroutine<>::operator()().
Important | |
---|---|
Code executed by coroutine must not prevent the propagation of the forced_unwind exception. Absorbing that exception will cause stack unwinding to fail. Thus, any code that catches all exceptions must rethrow the pending exception. |
try { // code that might throw } catch( forced_unwind) { throw; } catch(...) { // possibly not rethrow pending exception }
Sometimes it is necessary to unwind the stack of an unfinished coroutine to
destroy local stack variables so they can release allocated resources (RAII
pattern). The third argument of the coroutine constructor, do_unwind
,
indicates whether the destructor should unwind the stack (unwind stack as default).
Stack unwinding assumes the following preconditions:
After unwinding, a coroutine is complete.
typedef boost::coro::coroutine< void() > coro_t; struct X { X() { std::cout << "X()" << std::endl; } ~X() { std::cout << "~X()" << std::endl; } }; void fn() { X x; for( int i = 0;; ++i) { std::cout << "fn(): " << i << std::endl; // transfer execution control back to main() self.yield(); } } int main( int argc, char * argv[]) { { coro_t c( boost::bind( fn, _1), boost::coro::default_stacksize(), boost::coro::stack_unwind); for ( int i = 0; i < 5; ++i) { // transfer execution control to fn() c(); } std::cout << "c is complete: " << std::boolalpha << c.is_complete() << "\n"; } std::cout << "Done" << std::endl; return EXIT_SUCCESS; } output: X() fn(): 0 fn(): 1 fn(): 2 fn(): 3 fn(): 4 fn(): 5 c is complete: false ~X() Done
Important | |
---|---|
You must not swallow forced_unwind exceptions! |
Some applications do not use floating-point registers and can disable preserving fpu registers for perfromance reasons (see chapter performance).
Note | |
---|---|
In order to be combatible to the ABIs the FPU registers are preserved by default. |