...one of the most highly
regarded and expertly designed C++ library projects in the
world.
— Herb Sutter and Andrei
Alexandrescu, C++
Coding Standards
Boost.Coroutine provides two implementations - asymmetric and symmetric coroutines.
Symmetric coroutines occur usually in the context of concurrent programming in order to represent independent units of execution. Implementations that produce sequences of values typically use asymmetric coroutines. [5]
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.
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 behaviour.
On coroutine destruction the associated stack will be unwound.
The constructor of coroutine allows you to pass a customized stack-allocator. stack-allocator is free to deallocate the stack or cache it for future usage (for coroutines created later).
symmetric_coroutine<>::call_type, asymmetric_coroutine<>::push_type and asymmetric_coroutine<>::pull_type support segmented stacks (growing on demand).
It is not always possible to accurately estimate 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) [6] 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.
A coroutine saves and restores registers according to the underlying ABI on each context switch (using Boost.Context).
Some applications do not use floating-point registers and can disable preserving FPU registers for performance reasons.
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 symmetric_coroutine<>::call_type::operator(), asymmetric_coroutine<>::push_type::operator() and asymmetric_coroutine<>::pull_type::operator().
Warning | |
---|---|
Calling symmetric_coroutine<>::call_type::operator(), asymmetric_coroutine<>::push_type::operator() and asymmetric_coroutine<>::pull_type::operator() from inside the same coroutine results in undefined behaviour. |
As an example, the code below will result in undefined behaviour:
boost::coroutines::symmetric_coroutine<void>::call_type coro( [&](boost::coroutines::symmetric_coroutine<void>::yield_type& yield){ yield(coro); // yield to same symmetric_coroutine }); coro();
[5] Moura, Ana Lucia De and Ierusalimschy, Roberto. "Revisiting coroutines". ACM Trans. Program. Lang. Syst., Volume 31 Issue 2, February 2009, Article No. 6