Fibers are lightweight threads managed in user-space. Fibers from this library are:
-
Stackful.
-
Cooperative.
-
Use Boost.Asio execution contexts (e.g.
boost::asio::io_context
) as schedulers. Any async function will work as a suspension point. -
Non-symmetric. Main fiber and secondary fibers are exposed to different APIs. There is a clear separation between fibers that run inside and outside ASIO’s execution contexts.
trial::iofiber::fiber
is an alias for
trial::iofiber::basic_fiber<boost::asio::io_context::strand>
. To spawn a
fiber, pass a strand executor
and a fiber start-function
to fiber
constructor. Fiber will be scheduled through executor
and use it throughout
its lifetime and start-function
will be executed on top of fiber’s stack:
boost::asio::io_context ctx;
boost::asio::io_context::strand strand{ctx};
fiber(
strand,
[](fiber::this_fiber this_fiber) {
std::cout << "Hello World" << std::endl;
}
).detach();
ctx.run();
The start-function must have a fiber::this_fiber
parameter through which it
can have access to local/private[1] functions. There is a second constructor to instantiate a strand for
you:
boost::asio::io_context ctx;
fiber(
ctx,
[](fiber::this_fiber this_fiber) {
std::cout << "Hello World" << std::endl;
}
).detach();
ctx.run();
And there is a third constructor which will reuse the strand from the parent fiber:
boost::asio::io_context ctx;
fiber(
ctx,
[](fiber::this_fiber this_fiber) {
fiber(
this_fiber,
[](fiber::this_fiber this_fiber) {
std::cout << " World" << std::endl;
}
).detach();
std::cout << "Hello";
}
).detach();
ctx.run();
All fiber
objects must be either join()
'ed or detach()
'ed. If you
don’t call any of these functions, fiber
's destructor will call
strand.context().stop()
function to stop the application (a less severe
alternative to std::terminate()
). You can check whether the application
finished normally or abnormally using trial::iofiber::context_aborted()
.
Note
|
Implementation details
It follows a design similar to POSIX threads:
— pthread_join(3)
As of our fibers, each fiber will call
|
boost::asio::io_context ioctx;
fiber f1(
ioctx,
[](fib::fiber::this_fiber this_fiber) {
boost::asio::steady_timer timer{this_fiber.get_executor().context()};
std::cout << "3..." << std::flush;
timer.expires_after(std::chrono::seconds(1));
timer.async_wait(this_fiber);
std::cout << " 2..." << std::flush;
timer.expires_after(std::chrono::seconds(1));
timer.async_wait(this_fiber);
std::cout << " 1..." << std::endl;
timer.expires_after(std::chrono::seconds(1));
timer.async_wait(this_fiber);
}
);
fiber(
ioctx,
[&f1](fib::fiber::this_fiber this_fiber) {
f1.join(this_fiber);
std::cout << "Hello World" << std::endl;
}
).detach();
ioctx.run();
fiber::this_fiber
The this_fiber
object passed to the fiber start-function is a completion token
and you can use it with any Boost.Asio’s async_*
function.
void start(fiber::this_fiber this_fiber)
{
boost::asio::steady_timer timer{this_fiber.get_executor().context()};
timer.expires_after(std::chrono::seconds(1));
try {
timer.async_wait(this_fiber);
// ...
} catch (const boost::system::system_error& e) {
// ...
If you want to handle boost::system::error_code
errors directly instead having
them translated to exceptions, use the operator[]
:
void start(fiber::this_fiber this_fiber)
{
boost::asio::steady_timer timer{this_fiber.get_executor().context()};
timer.expires_after(std::chrono::seconds(1));
boost::system::error_code ec;
timer.async_wait(this_fiber[ec]);
Another ability this_fiber
provides you is the ability to do spurious yields:
this_fiber.yield();
See also:
join()
, the strand methods will be no-ops by the time boost::asio::io_context::run()
returns, so we need to keep’em busy.