Reputation: 6646
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
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
Reputation: 11317
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.
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