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

PrevUpHomeNext

when_any, simple completion

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.


PrevUpHomeNext