Zizheng Tai
Zizheng Tai

Reputation: 6646

Blocking on many locks/futures/etc. until any is ready

Is it possible to block on a group of locks/futures/any blockable entities, until any one of them is ready? The idea is that we can do:

std::vector<std::future<T>> futures = ...;
auto ready_future = wait_until_an_element_is_ready(futures);
process(ready_future.get());

I remember libraries like libevent, libev, and libuv have this kind of ability for IO tasks. But I don't know if these can be done for locks/futures.

A way I thought of to sort of achieve this is to have the futures call a handler upon completion, but at the same time compare-and-exchange the handler to null so that no other futures can call it. This requires the coordination of the futures, however, so can't be done for locks.

UPDATE: There seems to be a proposal for it for C++2x.

Upvotes: 12

Views: 1002

Answers (2)

Richard Hodges
Richard Hodges

Reputation: 69912

If you have boost available, its threading capabilities far exceed the standard library.

This will be the case after c++17, and by the looks of things, even after c++20.

#include <future>
#include <vector>

#define BOOST_THREAD_VERSION 4
#include <boost/thread.hpp>


void wait_for_any(std::vector<boost::future<void>> & v)
{
    boost::wait_for_any(std::begin(v), std::end(v));
}

Upvotes: 7

JVApen
JVApen

Reputation: 11317

Alternative 1:

Right now, this ain't possible, though, you could work around it by writing your own find-logic.

template<typename Iterator>
Iterator find_first_ready_future(Iterator begin, Iterator end)
{
    return std::find_if(begin, end, [](auto &future) { return future.wait_for(0s) == std::future_status::ready; }
}

template<typename T>
std::future<T> wait_until_an_element_is_ready(std::vector<std::future<T>> &vector)
{
    assert(!vector.empty());
    auto iterator = vector.end();
    do
    {
        // force switch of threads (if you don't want a busy loop, you might want to sleep this thread)
        // If reaction speed is very important, you might want to skip this first yield/sleep in the first iteration.
        std::this_thread::yield();

        iterator = find_first_ready_future(vector.begin(), vector.end());
    } while (iterator == vector.cend());
    auto result = std::move(*iterator);
    vector.erase(iterator); // Remove the ready future to prepare for the next call. (You ain't allowed to call .get() twice)
    return result;
}

Please note that all futures have to be created with the 'async' flag, because this will become an infinite loop if they are 'deferred'.

PS: If you don't want this to be blocking for your main thread, you might want to execute this in its own thread/future.

Alternative 2:

Another alternative would be to wrap your futures to execute the task. This is a bit similar to the future.then proposal:

template<typename T>
std::vector<std::future<void>> executeOnComplete(std::vector<std::future<T>> &&v)
{
    std::vector<std::future<void>> result;
    result.reserve(v.size());
    for (auto &f : v)
       result.emplace(std::async(std::launch::async,
                [f = std::move(f)] { process(f.get()); }));
    return result;
}

This alternative creates a new thread for each future and will block it until the original future is ready. It comes with the risk that you create too many threads.

PS: With some fancy result_of template logic, you can even create futures which return the result of `process

Upvotes: 0

Related Questions