440 lines
15 KiB
Plaintext
440 lines
15 KiB
Plaintext
[/
|
|
Copyright Oliver Kowalke 2009.
|
|
Distributed under the Boost Software License, Version 1.0.
|
|
(See accompanying file LICENSE_1_0.txt or copy at
|
|
http://www.boost.org/LICENSE_1_0.txt
|
|
]
|
|
|
|
[section:symmetric Symmetric coroutine]
|
|
|
|
In contrast to asymmetric coroutines, where the relationship between caller and
|
|
callee is fixed, symmetric coroutines are able to transfer execution control
|
|
to any other (symmetric) coroutine. E.g. a symmetric coroutine is not required
|
|
to return to its direct caller.
|
|
|
|
|
|
[heading __call_coro__]
|
|
__call_coro__ starts a symmetric coroutine and transfers its parameter to its
|
|
__coro_fn__.
|
|
The template parameter defines the transferred parameter type.
|
|
The constructor of __call_coro__ takes a function (__coro_fn__) accepting a
|
|
reference to a __yield_coro__ as argument. Instantiating a __call_coro__ does
|
|
not pass the control of execution to __coro_fn__ - instead the first call of
|
|
__call_coro_op__ synthesizes a __yield_coro__ and passes it as reference to
|
|
__coro_fn__.
|
|
|
|
The __call_coro__ interface does not contain a ['get()]-function: you can not
|
|
retrieve values from another execution context with this kind of coroutine
|
|
object.
|
|
|
|
|
|
[heading __yield_coro__]
|
|
__yield_coro_op__ is used to transfer data and execution control to another
|
|
context by calling __yield_coro_op__ with another __call_coro__ as first argument.
|
|
Alternatively, you may transfer control back to the code that called
|
|
__call_coro_op__ by calling __yield_coro_op__ without a __call_coro__ argument.
|
|
|
|
The class has only one template parameter defining the transferred parameter
|
|
type.
|
|
Data transferred to the coroutine are accessed through __yield_coro_get__.
|
|
|
|
[important __yield_coro__ can only be created by the framework.]
|
|
|
|
std::vector<int> merge(const std::vector<int>& a,const std::vector<int>& b)
|
|
{
|
|
std::vector<int> c;
|
|
std::size_t idx_a=0,idx_b=0;
|
|
boost::coroutines::symmetric_coroutine<void>::call_type* other_a=0,* other_b=0;
|
|
|
|
boost::coroutines::symmetric_coroutine<void>::call_type coro_a(
|
|
[&](boost::coroutines::symmetric_coroutine<void>::yield_type& yield) {
|
|
while(idx_a<a.size())
|
|
{
|
|
if(b[idx_b]<a[idx_a]) // test if element in array b is less than in array a
|
|
yield(*other_b); // yield to coroutine coro_b
|
|
c.push_back(a[idx_a++]); // add element to final array
|
|
}
|
|
// add remaining elements of array b
|
|
while ( idx_b < b.size())
|
|
c.push_back( b[idx_b++]);
|
|
});
|
|
|
|
boost::coroutines::symmetric_coroutine<void>::call_type coro_b(
|
|
[&](boost::coroutines::symmetric_coroutine<void>::yield_type& yield) {
|
|
while(idx_b<b.size())
|
|
{
|
|
if (a[idx_a]<b[idx_b]) // test if element in array a is less than in array b
|
|
yield(*other_a); // yield to coroutine coro_a
|
|
c.push_back(b[idx_b++]); // add element to final array
|
|
}
|
|
// add remaining elements of array a
|
|
while ( idx_a < a.size())
|
|
c.push_back( a[idx_a++]);
|
|
});
|
|
|
|
|
|
other_a = & coro_a;
|
|
other_b = & coro_b;
|
|
|
|
coro_a(); // enter coroutine-fn of coro_a
|
|
|
|
return c;
|
|
}
|
|
|
|
std::vector< int > a = {1,5,6,10};
|
|
std::vector< int > b = {2,4,7,8,9,13};
|
|
std::vector< int > c = merge(a,b);
|
|
print(a);
|
|
print(b);
|
|
print(c);
|
|
|
|
output:
|
|
a : 1 5 6 10
|
|
b : 2 4 7 8 9 13
|
|
c : 1 2 4 5 6 7 8 9 10 13
|
|
|
|
In this example two __call_coro__ are created in the main execution context
|
|
accepting a lambda function (== __coro_fn__) which merges elements of two
|
|
sorted arrays into a third array.
|
|
`coro_a()` enters the __coro_fn__ of `coro_a` cycling through the array and
|
|
testing if the actual element in the other array is less than the element in
|
|
the local one. If so, the coroutine yields to the other coroutine `coro_b`
|
|
using `yield(*other_b)`. If the current element of the local array is less
|
|
than the element of the other array, it is put to the third array.
|
|
Because the coroutine jumps back to `coro_a()` (returning from this method)
|
|
after leaving the __coro_fn__, the elements of the other array will appended
|
|
at the end of the third array if all element of the local array are processed.
|
|
|
|
|
|
[heading coroutine-function]
|
|
The __coro_fn__ returns ['void] and takes __yield_coro__, providing
|
|
coroutine functionality inside the __coro_fn__, as argument. Using this
|
|
instance is the only way to transfer data and execution control.
|
|
__call_coro__ does not enter the __coro_fn__ at __call_coro__ construction but
|
|
at the first invocation of __call_coro_op__.
|
|
|
|
Unless the template parameter is `void`, the __coro_fn__ of a
|
|
__call_coro__ can assume that (a) upon initial entry and (b) after every
|
|
__yield_coro_op__ call, its __yield_coro_get__ has a new value available.
|
|
|
|
However, if the template parameter is a move-only type,
|
|
__yield_coro_get__ may only be called once before the next __yield_coro_op__
|
|
call.
|
|
|
|
[heading passing data from main-context to a symmetric-coroutine]
|
|
In order to transfer data to a __call_coro__ from the main-context the
|
|
framework synthesizes a __yield_coro__ associated with the __call_coro__
|
|
instance. The synthesized __yield_coro__ is passed as argument to __coro_fn__.
|
|
The main-context must call __call_coro_op__ in order to transfer each data value
|
|
into the __coro_fn__.
|
|
Access to the transferred data value is given by __yield_coro_get__.
|
|
|
|
boost::coroutines::symmetric_coroutine<int>::call_type coro( // constructor does NOT enter coroutine-function
|
|
[&](boost::coroutines::symmetric_coroutine<int>::yield_type& yield){
|
|
for (;;) {
|
|
std::cout << yield.get() << " ";
|
|
yield(); // jump back to starting context
|
|
}
|
|
});
|
|
|
|
coro(1); // transfer {1} to coroutine-function
|
|
coro(2); // transfer {2} to coroutine-function
|
|
coro(3); // transfer {3} to coroutine-function
|
|
coro(4); // transfer {4} to coroutine-function
|
|
coro(5); // transfer {5} to coroutine-function
|
|
|
|
|
|
[heading exceptions]
|
|
An uncaught exception inside a __call_coro__'s __coro_fn__ will call
|
|
__terminate__.
|
|
|
|
[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 re-throw any
|
|
pending __forced_unwind__ exception.]
|
|
|
|
try {
|
|
// code that might throw
|
|
} catch(const boost::coroutines::detail::forced_unwind&) {
|
|
throw;
|
|
} catch(...) {
|
|
// possibly not re-throw pending exception
|
|
}
|
|
|
|
[important Do not jump from inside a catch block and then re-throw the
|
|
exception in another execution context.]
|
|
|
|
|
|
[heading Stack unwinding]
|
|
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 `attributes` argument of the coroutine constructor indicates
|
|
whether the destructor should unwind the stack (stack is unwound by default).
|
|
|
|
Stack unwinding assumes the following preconditions:
|
|
|
|
* The coroutine is not __not_a_coro__
|
|
* The coroutine is not complete
|
|
* The coroutine is not running
|
|
* The coroutine owns a stack
|
|
|
|
After unwinding, a __coro__ is complete.
|
|
|
|
struct X {
|
|
X(){
|
|
std::cout<<"X()"<<std::endl;
|
|
}
|
|
|
|
~X(){
|
|
std::cout<<"~X()"<<std::endl;
|
|
}
|
|
};
|
|
|
|
boost::coroutines::symmetric_coroutine<int>::call_type other_coro(...);
|
|
|
|
{
|
|
boost::coroutines::symmetric_coroutine<void>::call_type coro(
|
|
[&](boost::coroutines::symmetric_coroutine<void>::yield_type& yield){
|
|
X x;
|
|
std::cout<<"fn()"<<std::endl;
|
|
// transfer execution control to other coroutine
|
|
yield( other_coro, 7);
|
|
});
|
|
|
|
coro();
|
|
|
|
std::cout<<"coro is complete: "<<std::boolalpha<<!coro<<"\n";
|
|
}
|
|
|
|
output:
|
|
X()
|
|
fn()
|
|
coro is complete: false
|
|
~X()
|
|
|
|
|
|
[heading Exit a __coro_fn__]
|
|
__coro_fn__ is exited with a simple return statement. This jumps back to the
|
|
calling __call_coro_op__ at the start of symmetric coroutine chain. That is,
|
|
symmetric coroutines do not have a strong, fixed relationship to the caller as
|
|
do asymmetric coroutines. The __call_coro__ becomes complete, e.g.
|
|
__call_coro_bool__ will return `false`.
|
|
|
|
[important After returning from __coro_fn__ the __coro__ is complete (can not be
|
|
resumed with __call_coro_op__).]
|
|
|
|
|
|
[section:symmetric_coro Class `symmetric_coroutine<>::call_type`]
|
|
|
|
#include <boost/coroutine/symmetric_coroutine.hpp>
|
|
|
|
template< typename Arg >
|
|
class symmetric_coroutine<>::call_type
|
|
{
|
|
public:
|
|
call_type() noexcept;
|
|
|
|
template< typename Fn >
|
|
call_type( Fn && fn, attributes const& attr = attributes() );
|
|
|
|
template< typename Fn, typename StackAllocator >
|
|
call_type( Fn && fn, attributes const& attr, StackAllocator stack_alloc);
|
|
|
|
~call_type();
|
|
|
|
call_type( call_type const& other)=delete;
|
|
|
|
call_type & operator=( call_type const& other)=delete;
|
|
|
|
call_type( call_type && other) noexcept;
|
|
|
|
call_type & operator=( call_type && other) noexcept;
|
|
|
|
operator unspecified-bool-type() const;
|
|
|
|
bool operator!() const noexcept;
|
|
|
|
void swap( call_type & other) noexcept;
|
|
|
|
call_type & operator()( Arg arg) noexcept;
|
|
};
|
|
|
|
template< typename Arg >
|
|
void swap( symmetric_coroutine< Arg >::call_type & l, symmetric_coroutine< Arg >::call_type & r);
|
|
|
|
[heading `call_type()`]
|
|
[variablelist
|
|
[[Effects:] [Creates a coroutine representing __not_a_coro__.]]
|
|
[[Throws:] [Nothing.]]
|
|
]
|
|
|
|
[heading `template< typename Fn >
|
|
call_type( Fn fn, attributes const& attr)`]
|
|
[variablelist
|
|
[[Preconditions:] [`size` >= minimum_stacksize(), `size` <= maximum_stacksize()
|
|
when ! is_stack_unbounded().]]
|
|
[[Effects:] [Creates a coroutine which will execute `fn`. Argument `attr`
|
|
determines stack clean-up.
|
|
For allocating/deallocating the stack `stack_alloc` is used.]]
|
|
]
|
|
|
|
[heading `template< typename Fn, typename StackAllocator >
|
|
call_type( Fn && fn, attributes const& attr, StackAllocator const& stack_alloc)`]
|
|
[variablelist
|
|
[[Preconditions:] [`size` >= minimum_stacksize(), `size` <= maximum_stacksize()
|
|
when ! is_stack_unbounded().]]
|
|
[[Effects:] [Creates a coroutine which will execute `fn`. Argument `attr`
|
|
determines stack clean-up.
|
|
For allocating/deallocating the stack `stack_alloc` is used.]]
|
|
]
|
|
|
|
[heading `~call_type()`]
|
|
[variablelist
|
|
[[Effects:] [Destroys the context and deallocates the stack.]]
|
|
]
|
|
|
|
[heading `call_type( call_type && other)`]
|
|
[variablelist
|
|
[[Effects:] [Moves the internal data of `other` to `*this`.
|
|
`other` becomes __not_a_coro__.]]
|
|
[[Throws:] [Nothing.]]
|
|
]
|
|
|
|
[heading `call_type & operator=( call_type && other)`]
|
|
[variablelist
|
|
[[Effects:] [Destroys the internal data of `*this` and moves the
|
|
internal data of `other` to `*this`. `other` becomes __not_a_coro__.]]
|
|
[[Throws:] [Nothing.]]
|
|
]
|
|
|
|
[heading `operator unspecified-bool-type() const`]
|
|
[variablelist
|
|
[[Returns:] [If `*this` refers to __not_a_coro__ or the coroutine-function
|
|
has returned (completed), the function returns `false`. Otherwise `true`.]]
|
|
[[Throws:] [Nothing.]]
|
|
]
|
|
|
|
[heading `bool operator!() const`]
|
|
[variablelist
|
|
[[Returns:] [If `*this` refers to __not_a_coro__ or the coroutine-function
|
|
has returned (completed), the function returns `true`. Otherwise `false`.]]
|
|
[[Throws:] [Nothing.]]
|
|
]
|
|
|
|
[heading `void swap( call_type & other)`]
|
|
[variablelist
|
|
[[Effects:] [Swaps the internal data from `*this` with the values
|
|
of `other`.]]
|
|
[[Throws:] [Nothing.]]
|
|
]
|
|
|
|
[heading `call_type & operator()(Arg arg)`]
|
|
|
|
symmetric_coroutine::call_type& coroutine<Arg,StackAllocator>::call_type::operator()(Arg);
|
|
symmetric_coroutine::call_type& coroutine<Arg&,StackAllocator>::call_type::operator()(Arg&);
|
|
symmetric_coroutine::call_type& coroutine<void,StackAllocator>::call_type::operator()();
|
|
|
|
[variablelist
|
|
[[Preconditions:] [operator unspecified-bool-type() returns `true` for `*this`.]]
|
|
[[Effects:] [Execution control is transferred to __coro_fn__ and the argument
|
|
`arg` is passed to the coroutine-function.]]
|
|
[[Throws:] [Nothing.]]
|
|
]
|
|
|
|
[heading Non-member function `swap()`]
|
|
|
|
template< typename Arg >
|
|
void swap( symmetric_coroutine< Arg >::call_type & l, symmetric_coroutine< Arg >::call_type & r);
|
|
|
|
[variablelist
|
|
[[Effects:] [As if 'l.swap( r)'.]]
|
|
]
|
|
|
|
[endsect]
|
|
|
|
|
|
|
|
[section:yield_coro Class `symmetric_coroutine<>::yield_type`]
|
|
|
|
#include <boost/coroutine/symmetric_coroutine.hpp>
|
|
|
|
template< typename R >
|
|
class symmetric_coroutine<>::yield_type
|
|
{
|
|
public:
|
|
yield_type() noexcept;
|
|
|
|
yield_type( yield_type const& other)=delete;
|
|
|
|
yield_type & operator=( yield_type const& other)=delete;
|
|
|
|
yield_type( yield_type && other) noexcept;
|
|
|
|
yield_type & operator=( yield_type && other) noexcept;
|
|
|
|
void swap( yield_type & other) noexcept;
|
|
|
|
operator unspecified-bool-type() const;
|
|
|
|
bool operator!() const noexcept;
|
|
|
|
yield_type & operator()();
|
|
|
|
template< typename X >
|
|
yield_type & operator()( symmetric_coroutine< X >::call_type & other, X & x);
|
|
|
|
template< typename X >
|
|
yield_type & operator()( symmetric_coroutine< X >::call_type & other);
|
|
|
|
R get() const;
|
|
};
|
|
|
|
[heading `operator unspecified-bool-type() const`]
|
|
[variablelist
|
|
[[Returns:] [If `*this` refers to __not_a_coro__, the function returns `false`.
|
|
Otherwise `true`.]]
|
|
[[Throws:] [Nothing.]]
|
|
]
|
|
|
|
[heading `bool operator!() const`]
|
|
[variablelist
|
|
[[Returns:] [If `*this` refers to __not_a_coro__, the function returns `true`.
|
|
Otherwise `false`.]]
|
|
[[Throws:] [Nothing.]]
|
|
]
|
|
|
|
[heading `yield_type & operator()()`]
|
|
|
|
yield_type & operator()();
|
|
template< typename X >
|
|
yield_type & operator()( symmetric_coroutine< X >::call_type & other, X & x);
|
|
template<>
|
|
yield_type & operator()( symmetric_coroutine< void >::call_type & other);
|
|
|
|
[variablelist
|
|
[[Preconditions:] [`*this` is not a __not_a_coro__.]]
|
|
[[Effects:] [The first function transfers execution control back to the starting point,
|
|
e.g. invocation of __call_coro_op__. The last two functions transfer the execution control
|
|
to another symmetric coroutine. Parameter `x` is passed as value into `other`'s context.]]
|
|
[[Throws:] [__forced_unwind__]]
|
|
]
|
|
|
|
[heading `R get()`]
|
|
|
|
R symmetric_coroutine<R>::yield_type::get();
|
|
R& symmetric_coroutine<R&>::yield_type::get();
|
|
void symmetric_coroutine<void>yield_type::get()=delete;
|
|
|
|
[variablelist
|
|
[[Preconditions:] [`*this` is not a __not_a_coro__.]]
|
|
[[Returns:] [Returns data transferred from coroutine-function via
|
|
__call_coro_op__.]]
|
|
[[Throws:] [`invalid_result`]]
|
|
]
|
|
|
|
[endsect]
|
|
|
|
|
|
|
|
[endsect]
|