Boost
C++ Libraries
...one of the most highly
regarded and expertly designed C++ library projects in the
world.
— Herb Sutter and Andrei
Alexandrescu, C++
Coding Standards
This is an older version of Boost and was released in 2018. The current version is 1.90.0.
The simplest case is when you only need to know that the first of a set of asynchronous tasks has completed — but you don't need to obtain a return value, and you're confident that they will not throw exceptions.
For this we introduce a Done
class to wrap a bool variable
with a condition_variable and a mutex:
// Wrap canonical pattern for condition_variable + bool flag struct Done { private: boost::fibers::condition_variable cond; boost::fibers::mutex mutex; bool ready = false; public: typedef std::shared_ptr< Done > ptr; void wait() { std::unique_lock< boost::fibers::mutex > lock( mutex); cond.wait( lock, [this](){ return ready; }); } void notify() { { std::unique_lock< boost::fibers::mutex > lock( mutex); ready = true; } // release mutex cond.notify_one(); } };
The pattern we follow throughout this section is to pass a std::shared_ptr<>
to the relevant synchronization object to the various tasks' fiber functions.
This eliminates nagging questions about the lifespan of the synchronization
object relative to the last of the fibers.
wait_first_simple() uses that tactic for Done:
template< typename ... Fns > void wait_first_simple( Fns && ... functions) { // Use shared_ptr because each function's fiber will bind it separately, // and we're going to return before the last of them completes. auto done( std::make_shared< Done >() ); wait_first_simple_impl( done, std::forward< Fns >( functions) ... ); done->wait(); }
wait_first_simple_impl() is an ordinary recursion over the argument
pack, capturing Done::ptr for each new fiber:
// Degenerate case: when there are no functions to wait for, return // immediately. void wait_first_simple_impl( Done::ptr) { } // When there's at least one function to wait for, launch it and recur to // process the rest. template< typename Fn, typename ... Fns > void wait_first_simple_impl( Done::ptr done, Fn && function, Fns && ... functions) { boost::fibers::fiber( [done, function](){ function(); done->notify(); }).detach(); wait_first_simple_impl( done, std::forward< Fns >( functions) ... ); }
The body of the fiber's lambda is extremely simple, as promised: call the
function, notify Done
when it returns. The first fiber to do so allows wait_first_simple() to return — which is why it's useful to
have std::shared_ptr<Done>
manage the lifespan of our Done
object rather than declaring it as a stack variable in wait_first_simple().
This is how you might call it:
wait_first_simple( [](){ sleeper("wfs_long", 150); }, [](){ sleeper("wfs_medium", 100); }, [](){ sleeper("wfs_short", 50); });
In this example, control resumes after wait_first_simple() when sleeper("wfs_short",
50)
completes — even though the other two sleeper() fibers are still running.