Boost C++ Libraries Home Libraries People FAQ More

PrevUpHomeNext

Coroutine

Class coroutine

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] Note

Boost.Move is used to emulate rvalue references.

[Warning] Warning

If coroutine is used in a multithreaded application, it can migrated between threads, but must not reference thread-local storage.

[Note] Note

If fiber-local storage is used on Windows, the user is responsible for calling ::FlsAlloc(), ::FlsFree().

Executing a coroutine

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] 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] Warning

Calling coroutine<>::operator()() from inside the same coroutine results in undefined behaviour.

[Note] 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.

Transfer of data

The first template argument of coroutine, Signature, defines the signature of the coroutine-function and its return type.

[Note] 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

coroutine-function with multiple arguments

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

Exit a coroutine-function

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

Exceptions in coroutine-function

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] 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
}

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 (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] Important

You must not swallow forced_unwind exceptions!

FPU preserving

Some applications do not use floating-point registers and can disable preserving fpu registers for perfromance reasons (see chapter performance).

[Note] Note

In order to be combatible to the ABIs the FPU registers are preserved by default.


PrevUpHomeNext