ahlougha shad
ahlougha shad

Reputation: 21

What is the optimal way to wait for multiple futures in C++11?

I am looking for the optimal way in terms of execution time, to wait for independent futures to finish.

Dealing with only two futures is simple, one can have the optimal way as follows:

auto f1 = async(launch::async, []{ doSomething(’.’); }); 
auto f2 = async(launch::async, []{ doSomething(’+’); });

while (f1.wait_for(chrono::seconds(0)) != future_status::ready && f2.wait_for(chrono::seconds(0)) != future_status::ready) 
{ };

f1.get();
f2.get();

This way, we leave the loop while with at least one of the futures is finished, then calling .get() for both won't make the program looses time.

How about n futures?

Upvotes: 2

Views: 4123

Answers (1)

hegel5000
hegel5000

Reputation: 884

If you want to create an arbitrary number of std::futures, it might help to put them all into a std::vector. You can then loop through the vector and get the results. Note that get handles waiting.

//Result type defined here: http://en.cppreference.com/w/cpp/thread/async
template<typename F, typename... Args>
using AsyncResult = std::result_of_t<std::decay_t<F>(std::decay_t<Args>...)>;

template<typename T, typename F1, typename F2>
void do_par(const std::vector<T>& ts, F1&& on_t, F2&& accumulate) {
  //The standard doesn't require that these futures correspond to individual
  //threads, but there's a good chance they'll be implemented that way.
  std::vector<std::future<AsyncResult<F1, T>>> threads;
  for (const T& t : ts) { threads.push_back(std::async(on_t, t)); }
  for (auto& future : threads) { accumulate(std::move(future)); }
}

template<typename T, typename F>
std::vector<AsyncResult<F, T>> map_par(const std::vector<T>& ts, F&& on_t) {
  std::vector<AsyncResult<F, T>> out;
  do_par(ts, on_t, [&](auto&& future_){ 
    out.push_back(future_.get()); //Think of this as just waiting on each thread to finish.
  });
  return out;
}

std::string doSomething(const std::string&){ return std::string("yo"); }

And then you can do

const std::vector<std::string> results = map_par(
  std::vector<std::string>{".", "+", "et al"}, doSomething
);

This simple map_par function isn't quite the savviest solution. It might help to set up a thread queue (which itself would own a thread pool) to cut out the overhead of spawning individual threads, as well as the context-switching overhead which comes into play when you have more threads than CPU cores. Your thread queue implementation might want to have its own async method, akin to std::async.

If you want to use results immediately when they come in, regardless of input order, consider setting up a single-reader-multiple-writer (which incidentally also involves a queue).

std::condition_variable helps with both of the above suggestions.

Upvotes: 1

Related Questions