![]() |
Home | Libraries | People | FAQ | More |
As always with cooperative concurrency, it is important not to let any one fiber monopolize the processor too long: that could “starve” other ready fibers. This section discusses a couple of solutions.
Consider a classic event-driven program, organized around a main loop that fetches and dispatches incoming I/O events. You are introducing Boost.Fiber because certain asynchronous I/O sequences are logically sequential, and for those you want to write and maintain code that looks and acts sequential.
You are launching fibers on the application's main thread because certain of their actions will affect its user interface, and the application's UI framework permits UI operations only on the main thread. Or perhaps those fibers need access to main-thread data, and it would be too expensive in runtime (or development time) to robustly defend every such data item with thread synchronization primitives.
You must ensure that the application's main loop itself doesn't monopolize the processor: that the fibers it launches will get the CPU cycles they need.
The solution is the same as for any fiber that might claim the CPU for an extended
time: introduce calls to this_fiber::yield(). The most straightforward
approach is to call yield()
on every iteration of your existing main loop. In effect, this unifies the
application's main loop with Boost.Fiber's
internal main loop. yield()
allows the fiber manager to run any fibers that have become ready since the
previous iteration of the application's main loop. When these fibers have had
a turn, control passes to the thread's main fiber, which returns from yield() and
resumes the application's main loop.
More challenging is when the application's main loop is embedded in some other
library or framework. Such an application will typically, after performing
all necessary setup, pass control to some form of run() function from which control does not return
until application shutdown.
A Boost.Asio
program might call io_service::run()
in this way.
The trick here is to arrange to pass control to this_fiber::yield() frequently.
You can use an Asio
timer for this purpose. Instantiate the timer, arranging to call a
handler function when the timer expires:
[run_service]
The handler function calls yield(), then resets the timer and arranges to wake
up again on expiration:
[timer_handler]
Then instead of directly calling io_service::run(),
your application would call the above run_service(io_service&) wrapper.
Since, in this example, we always pass control to the fiber manager via yield(),
the calling fiber is never blocked. Therefore there is always at least one
ready fiber. Therefore the fiber manager never sleeps.
Using std::chrono::seconds(0) for every
keepalive timer interval would be unfriendly to other threads. When all I/O
is pending and all fibers are blocked, the io_service and the fiber manager
would simply spin the CPU, passing control back and forth to each other. Resetting
the timer for keepalive_iterval
allows tuning the responsiveness of this thread relative to others in the same
way as when Boost.Fiber is running without
Boost.Asio.
The source code above is found in round_robin.hpp.