Boost C++ Libraries

...one of the most highly regarded and expertly designed C++ library projects in the world. Herb Sutter and Andrei Alexandrescu, C++ Coding Standards

PrevUpHomeNext

Unidirectional coroutine

Class coroutine<>::pull_type
Class coroutine<>::push_type
[Note] Note

This is the default interface (macro BOOST_COROUTINES_UNIDIRECT).

Two coroutine types - coroutine<>::push_type and coroutine<>::pull_type - providing a unidirectional transfer of data.

coroutine<>::pull_type

coroutine<>::pull_type transfers data from another execution context (== pulled-from). The class has only one template parameter defining the transferred parameter type. The constructor of coroutine<>::pull_type takes a function (coroutine-function) accepting a reference to a coroutine<>::push_type as argument. Instantiating a coroutine<>::pull_type passes the control of execution to coroutine-function and a complementary coroutine<>::push_type is synthesized by the runtime and passed as reference to coroutine-function.

This kind of coroutine provides boost::coroutines::coroutine<>::pull_type::operator(). This method only switches context; it transfers no data.

coroutine<>::pull_type provides input iterators (boost::coroutines::coroutine<>::pull_type::iterator) and boost::begin()/boost::end() are overloaded. The increment-operation switches the context and transfers data.

boost::coroutines::coroutine<int>::pull_type source(
    [&](boost::coroutines::coroutine<int>::push_type& sink){
        int first=1,second=1;
        sink(first);
        sink(second);
        for(int i=0;i<8;++i){
            int third=first+second;
            first=second;
            second=third;
            sink(third);
        }
    });

for(auto i:source)
    std::cout << i <<  " ";

std::cout << "\nDone" << std::endl;

output:
1 1 2 3 5 8 13 21 34 55
Done

In this example a coroutine<>::pull_type is created in the main execution context taking a lambda function (== coroutine-function) which calculates Fibonacci numbers in a simple for-loop). The coroutine-function is executed in a newly created execution context which is managed by the instance of coroutine<>::pull_type. A coroutine<>::push_type is automatically generated by the runtime and passed as reference to the lambda function. Each time the lambda function calls boost::coroutines::coroutine<>::push_type::operator() with another Fibonacci number, coroutine<>::push_type transfers it back to the main execution context. The local state of coroutine-function is preserved and will be restored upon transferring execution control back to coroutine-function to calculate the next Fibonacci number. Because coroutine<>::pull_type provides input iterators and boost::begin()/boost::end() are overloaded, a range-based for-loop can be used to iterate over the generated Fibonacci numbers.

coroutine<>::push_type

coroutine<>::push_type transfers data to the other execution context (== pushed-to). The class has only one template parameter defining the transferred parameter type. The constructor of coroutine<>::push_type takes a function (coroutine-function) accepting a reference to a coroutine<>::pull_type as argument. In contrast to coroutine<>::pull_type, instantiating a coroutine<>::push_type does not pass the control of execution to coroutine-function - instead the first call of boost::coroutines::coroutine<>::push_type::operator() synthesizes a complementary coroutine<>::pull_type and passes it as reference to coroutine-function.

The interface does not contain a get()-function: you can not retrieve values from another execution context with this kind of coroutine.

coroutine<>::push_type provides output iterators (__push_coro__iterator) and boost::begin()/boost::end() are overloaded. The increment-operation switches the context and transfers data.

struct FinalEOL{
    ~FinalEOL(){
        std::cout << std::endl;
    }
};

const int num=5, width=15;
boost::coroutines::coroutine<std::string>::push_type writer(
    [&](boost::coroutines::coroutine<std::string>::pull_type& in){
        // finish the last line when we leave by whatever means
        FinalEOL eol;
        // pull values from upstream, lay them out 'num' to a line
        for (;;){
            for(int i=0;i<num;++i){
                // when we exhaust the input, stop
                if(!in) return;
                std::cout << std::setw(width) << in.get();
                // now that we've handled this item, advance to next
                in();
            }
            // after 'num' items, line break
            std::cout << std::endl;
        }
    });

std::vector<std::string> words{
    "peas", "porridge", "hot", "peas",
    "porridge", "cold", "peas", "porridge",
    "in", "the", "pot", "nine",
    "days", "old" };

std::copy(boost::begin(words),boost::end(words),boost::begin(writer));

output:
           peas       porridge            hot           peas       porridge
           cold           peas       porridge             in            the
            pot           nine           days            old

In this example a coroutine<>::push_type is created in the main execution context accepting a lambda function (== coroutine-function) which requests strings and lays out 'num' of them on each line. This demonstrates the inversion of control permitted by coroutines. Without coroutines, a utility function to perform the same job would necessarily accept each new value as a function parameter, returning after processing that single value. That function would depend on a static state variable. A coroutine-function, however, can request each new value as if by calling a function -- even though its caller also passes values as if by calling a function. The coroutine-function is executed in a newly created execution context which is managed by the instance of coroutine<>::push_type. The main execution context passes the strings to the coroutine-function by calling boost::coroutines::coroutine<>::push_type::operator(). A coroutine<>::pull_type is automatically generated by the runtime and passed as reference to the lambda function. The coroutine-function accesses the strings passed from the main execution context by calling boost::coroutines::coroutine<>::pull_type::get() and lays those strings out on std::cout according the parameters 'num' and 'width'. The local state of coroutine-function is preserved and will be restored after transferring execution control back to coroutine-function. Because coroutine<>::push_type provides output iterators and boost::begin()/boost::end() are overloaded, the std::copy algorithm can be used to iterate over the vector containing the strings and pass them one by one to the coroutine.

stackful

Each instance of a coroutine has its own stack.

In contrast to stackless coroutines, stackful coroutines allow invoking the suspend operation out of arbitrary sub-stackframes, enabling escape-and-reenter recursive operations.

move-only

A coroutine is moveable-only.

If it were copyable, then its stack with all the objects allocated on it would be copied too. That would force undefined behaviour if some of these objects were RAII-classes (manage a resource via RAII pattern). When the first of the coroutine copies terminates (unwinds its stack), the RAII class destructors will release their managed resources. When the second copy terminates, the same destructors will try to doubly-release the same resources, leading to undefined behavior.

clean-up

On coroutine destruction the associated stack will be unwound.

The constructor of coroutine allows to pass an customized stack-allocator. stack-allocator is free to deallocate the stack or cache it for future usage (for coroutines created later).

segmented stack

coroutine<>::push_type and coroutine<>::pull_type does support segmented stacks (growing on demand).

It is not always possible to estimated the required stack size - in most cases too much memory is allocated (waste of virtual address-space).

At construction a coroutine starts with a default (minimal) stack size. This minimal stack size is the maximum of page size and the canonical size for signal stack (macro SIGSTKSZ on POSIX).

At this time of writing only GCC (4.7)\cite{gccsplit} is known to support segmented stacks. With version 1.54 Boost.Coroutine provides support for segmented stacks.

The destructor releases the associated stack. The implementer is free to deallocate the stack or to cache it for later usage.

context switch

A coroutine saves and restores registers according to the underlying ABI on each context switch.

Some applications do not use floating-point registers and can disable preserving fpu registers for performance reasons.

[Note] Note

According to the calling convention the FPU registers are preserved by default.

On POSIX systems, the coroutine context switch does not preserve signal masks for performance reasons.

A context switch is done via boost::coroutines::coroutine<>::push_type::operator() and boost::coroutines::coroutine<>::pull_type::operator().

[Warning] Warning

Calling boost::coroutines::coroutine<>::push_type::operator()/boost::coroutines::coroutine<>::pull_type::operator() from inside the same coroutine results in undefined behaviour.

coroutine-function

The coroutine-function returns void and takes its counterpart-coroutine as argument, so that using the coroutine passed as argument to coroutine-function is the only way to transfer data and execution control back to the caller. Both coroutine types take the same template argument. For coroutine<>::pull_type the coroutine-function is entered at coroutine<>::pull_type construction. For coroutine<>::push_type the coroutine-function is not entered at coroutine<>::push_type construction but entered by the first invocation of boost::coroutines::coroutine<>::push_type::operator(). After execution control is returned from coroutine-function the state of the coroutine can be checked via boost::coroutines::coroutine<>::pull_type::operator bool returning true if the coroutine is still valid (coroutine-function has not terminated). Unless T is void, true also implies that a data value is available.

passing data from a pull-coroutine to main-context

In order to transfer data from a coroutine<>::pull_type to the main-context the framework synthesizes a coroutine<>::push_type associated with the coroutine<>::pull_type instance in the main-context. The synthesized coroutine<>::push_type is passed as argument to coroutine-function.\ The coroutine-function must call this boost::coroutines::coroutine<>::push_type::operator() in order to transfer each data value back to the main-context. In the main-context, the boost::coroutines::coroutine<>::pull_type::operator bool determines whether the coroutine is still valid and a data value is available or coroutine-function has terminated (coroutine<>::pull_type is invalid; no data value available). Access to the transferred data value is given by boost::coroutines::coroutine<>::pull_type::get().

boost::coroutines::coroutine<int>::pull_type source( // constructor enters coroutine-function
    [&](boost::coroutines::coroutine<int>::push_type& sink){
        sink(1); // push {1} back to main-context
        sink(1); // push {1} back to main-context
        sink(2); // push {2} back to main-context
        sink(3); // push {3} back to main-context
        sink(5); // push {5} back to main-context
        sink(8); // push {8} back to main-context
    });

while(source){            // test if pull-coroutine is valid
    int ret=source.get(); // access data value
    source();             // context-switch to coroutine-function
}
passing data from main-context to a push-coroutine

In order to transfer data to a coroutine<>::push_type from the main-context the framework synthesizes a coroutine<>::pull_type associated with the coroutine<>::push_type instance in the main-context. The synthesized coroutine<>::pull_type is passed as argument to coroutine-function. The main-context must call this boost::coroutines::coroutine<>::push_type::operator() in order to transfer each data value into the coroutine-function. Access to the transferred data value is given by boost::coroutines::coroutine<>::pull_type::get().

boost::coroutines::coroutine<int>::push_type sink( // constructor does NOT enter coroutine-function
    [&](boost::coroutines::coroutine<int>::pull_type& source){
        for (int i:source) {
            std::cout << i <<  " ";
        }
    });

std::vector<int> v{1,1,2,3,5,8,13,21,34,55};
for( int i:v){
    sink(i); // push {i} to coroutine-function
}
accessing parameters

Parameters returned from or transferred to the coroutine-function can be accessed with boost::coroutines::coroutine<>::pull_type::get().

Splitting-up the access of parameters from context switch function enables to check if coroutine<>::pull_type is valid after return from boost::coroutines::coroutine<>::pull_type::operator(), e.g. coroutine<>::pull_type has values and coroutine-function has not terminated.

boost::coroutines::coroutine<boost::tuple<int,int>>::push_type sink(
    [&](boost::coroutines::coroutine<boost::tuple<int,int>>::pull_type& source){
        // access tuple {7,11}; x==7 y==1
        int x,y;
        boost::tie(x,y)=source.get();
    });

sink(boost::make_tuple(7,11));
exceptions

An exception thrown inside a coroutine<>::pull_type's coroutine-function before its first call to boost::coroutines::coroutine<>::push_type::operator() will be re-thrown by the coroutine<>::pull_type constructor. After a coroutine<>::pull_type's coroutine-function's first call to boost::coroutines::coroutine<>::push_type::operator(), any subsequent exception inside that coroutine-function will be re-thrown by boost::coroutines::coroutine<>::pull_type::operator(). boost::coroutines::coroutine<>::pull_type::get() does not throw.

An exception thrown inside a coroutine<>::push_type's coroutine-function will be re-thrown by boost::coroutines::coroutine<>::push_type::operator().

[Important] Important

Code executed by coroutine must not prevent the propagation of the boost::coroutines::detail::forced_unwind exception. Absorbing that exception will cause stack unwinding to fail. Thus, any code that catches all exceptions must re-throw the pending exception.

try {
    // code that might throw
} catch(const forced_unwind&) {
    throw;
} catch(...) {
    // possibly not re-throw pending exception
}
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 third argument of the coroutine constructor, do_unwind, indicates whether the destructor should unwind the stack (stack is unwound by default).

Stack unwinding assumes the following preconditions:

After unwinding, a coroutine is complete.

struct X {
    X(){
        std::cout<<"X()"<<std::endl;
    }

    ~X(){
        std::cout<<"~X()"<<std::endl;
    }
};

{
    boost::coroutines::coroutine<void>::push_type sink(
        [&](boost::coroutines::coroutine<void>::pull_type& source){
            X x;
            for(int=0;;++i){
                std::cout<<"fn(): "<<i<<std::endl;
                // transfer execution control back to main()
                source();
            }
        });

    sink();
    sink();
    sink();
    sink();
    sink();

    std::cout<<"c is complete: "<<std::boolalpha<<c.is_complete()<<"\n";
}

std::cout<<"Done"<<std::endl;

output:
    X()
    fn(): 0
    fn(): 1
    fn(): 2
    fn(): 3
    fn(): 4
    fn(): 5
    c is complete: false
    ~X()
    Done
Range iterators

Boost.Coroutine provides output- and input-iterators using Boost.Range. coroutine<>::pull_type can be used via input-iterators using boost::begin() and boost::end().

int number=2,exponent=8;
boost::coroutines::coroutine< int >::pull_type source(
    [&]( boost::coroutines::coroutine< int >::push_type & sink){
        int counter=0,result=1;
        while(counter++<exponent){
            result=result*number;
            sink(result);
        }
    });

for (auto i:source)
    std::cout << i <<  " ";

std::cout << "\nDone" << std::endl;

output:
    2 4 8 16 32 64 128 256
    Done

Output-iterators can be created from coroutine<>::push_type.

boost::coroutines::coroutine<int>::push_type sink(
    [&](boost::coroutines::coroutine<int>::pull_type& source){
        while(source){
            std::cout << source.get() <<  " ";
            source();
        }
    });

std::vector<int> v{1,1,2,3,5,8,13,21,34,55};
std::copy(boost::begin(v),boost::end(v),boost::begin(sink));
Exit a coroutine-function

coroutine-function is exited with a simple return statement jumping back to the calling routine. The coroutine<>::pull_type/coroutine<>::push_type becomes complete, e.g. boost::coroutines::coroutine<>::operator bool will return 'false'.

[Important] Important

After returning from coroutine-function the coroutine is complete (can not resumed with boost::coroutines::coroutine<>::operator()).


PrevUpHomeNext