The initial version of Boost.Coroutine reference counted the
coroutine class template. Also the
was an alias for the
coroutine class itself. The rationale was
that, when used in a symmetric coroutine design it would be easy for a
coroutine to pass a copy of itself to other coroutines without needing
any explicit memory management. When all other coroutines dropped all
references to a specific coroutine that was deleted. Unfortunately
this same desirable behavior could backfire horribly if a cycle of
coroutines where to be formed.
In the end reference counting behavior was removed from the coroutine
interface andcoroutine where made movable. The same change lead to
the creation of coroutine::self to segregate coroutine
body specific operations (like yield and yield_to). Internally
reference counting is still used to manage coroutine lifetime when
future are used. While this can still lead to cycles if a coroutine
stores the result of
coro::make_callback() in a local, this is
explicitly prohibited in the interface, and should look suspiciously
wrong in code.
Futures were made movable for similar reasons.
Boost.Coroutine provides no way to retrieve a reference to the current
coroutine. This is first of all for reasons of type safety. Every
coroutine is typed on its signature, so would be current pointer. The
user of an hypothetical
current_coroutine would need to pass to this
function, as a template parameter, the signature of the coroutine that
should be extracted. This signature would be checked at run time with
the signature of the current coroutine. Given that
would be most useful in generic code, the signature would need to be
passed down to the to the function that need to access the current
coroutine. At this point there is little benefit on passing only the
signature instead of a reference to
The second reason is that
current_coroutine is a global object in
disguise. Global objects lead often to non scalable code. During the
development of the library and during testing, is has always been
possible to do away with the need for such a global by exploring other
Win32 fiber API provides a symmetric coroutine
interface with such a global object. Coding around the interface
mismatch between the Boost.Coroutine
API and the
fiber API has
been difficult and a potential source of
The last reason for not providing a
current_coroutine is that this
could be used to
yield. Suppose a coroutine that is manipulating
some shared data calls a seemingly innocuous function; this coroutine
current_coroutine().yield(), thus relinquishing control
and leaving the shared state with an invalid invariant. Functions that
may cause a coroutine to yield should documented as such. With the
current interface, these functions need a reference to
such a reference is a strong hint that the function might yield.
The main context is the flow of control outside of any coroutine
body. It is the flow of control started by
main() or from the
startup of any threads. Some coroutine APIs treat the main
context itself as a coroutine. Such libraries usually provide
symmetric coroutines, and treating
main() as a coroutine is the only
way to return to the main context. Boost.Coroutine is mostly designed around
asymmetric coroutines, so a normal
yield() can be used to return to
the main context.
main() as a coroutine also opens many problems:
coroutine<void()>, but this seems too arbitrary.
self. A default constructed
selfis not a solution, because it breaks the invariant that two
selfobjects always refer to two different objects. We have already reject the solution of a
init_main()function. This cannot be done statically because it must be done for each new thread. Leaving the responsibility to the users of the library opens the problem of two libraries trying both to initialize the current context.
It has been argued [Moura04] that asymmetric coroutines are the best coroutine abstraction, because are simpler and safer than symmetric coroutines, while having the same expressiveness. We agree with that and the library has been developed around an asymmetric design.
During development was apparent that symmetric functionality could be
added without compromising the
yield_to shouldn't be abused, it might
simplify some scenarios. It might also be a performance optimization.
"Premature optimization is the root of all evil" --
C. A. R. Hoare.|
While working on the Boost.Asio integration, the author thought that the only way to get good dispatching performance would be to use a specialized scheduler that used
Most cooperative threading libraries (for example the Pth library) deal with blocking behavior by wrapping asynchronous call behind a synchronous interface in the belief that asynchronous calls are a source of problems. Your author instead believes that are not the asynchronous calls themselves that complicate code, but the need to divide related code into multiple independent callback functions. Thus Boost.Coroutine doesn't try to hide the powerful Boost.Asio asynchronous interface behind a synchronous one, but simply helps dealing with the control inversion complication caused by the proliferation of small callbacks.
In fact _coroutines_ are not meant to be the silver bullet. Sometimes separated callbacks (maybe even defined in line with the help of Boost.Bind or Boost.Lambda) might be the right solution. One can even mix both styles together and use the best tool for each specific job.
It follows from the previous point that Boost.Coroutine is not a generalized
asynchronous framework. Do not confuse
wait as a general purpose
demultiplexer. The ability to wait for multiple futures is provided to
simplify some scenarios, like performing an operation while waiting
for a timer to expire, or reading and writing from two different
pipes. A coroutine that routinely waits for more that two or three futures,
should probably refactored in multiple coroutines.
|Copyright © 2006 Giovanni P. Deretta|