【问题标题】:Wait for the first future to complete in C++等待第一个未来在 C++ 中完成
【发布时间】:2020-07-05 03:20:36
【问题描述】:

假设我有两个期货对应于两个并行执行的计算。我如何等到第一个未来准备好?理想情况下,我正在寻找类似于Python asyncio's wait 的API,参数为return_when=FIRST_COMPLETED

但是,据我所知,当前的 C++ std::future 功能似乎不提供这样的 API。在这种情况下,我正在寻找一种有效/正确的方法来轮询未来的状态。

如果 future 不是此任务的正确工具,我愿意接受使用线程和条件变量的解决方案。

【问题讨论】:

  • std::future_status status = future->wait_for(0); ?
  • 您可以使用std::future<T>::get() 函数从该未来检索执行结果。它将等待未来的有效结果。
  • 只是好奇,但这似乎是一个奇怪的要求。如果有一个任务可以在例如第一个未来准备就绪时启动,那么为什么不将该任务包含在与该未来关联的线程中呢?就目前而言,这似乎有点XY。你有这方面的用例吗?
  • @G.M.澄清一下,要求是在两个任务中的任何一个完成后立即执行一项任务(而不是第一个任务/未来)。我当前的用例是在一个协议的实现中,Alice 和 Bob 向 Charlie 发送相同的数据,而 Charlie 在收到 Alice 或 Bob 的数据后立即继续计算,而无需等待他们两个都发送。我相信这是结合大多数语言提供的期货的一般方式。

标签: c++ multithreading asynchronous


【解决方案1】:

我试图想出一个函数,它接受两个期货 f1f2 并返回一个新的未来,它被解析为 f1f2,具体取决于哪个先完成。

该函数假定get() 方法不会在任何地方对f1f2 调用(否则我们需要使用std::shared_future)。

#include <unistd.h>

#include <array>
#include <future>
#include <iostream>
#include <thread>

template <typename T>
class WaitFirstData {
 public:
  T res;
  bool res_ready;
  std::condition_variable res_cv;
  std::mutex res_mutex;

  WaitFirstData() : res_ready{false} {}
};

template <typename T>
std::future<T> wait_first(std::future<T> f1, std::future<T> f2) {
  auto data = std::make_shared<WaitFirstData<T>>();

  std::promise<T> wait_first_promise;
  std::future<T> wait_first_future = wait_first_promise.get_future();

  std::thread wait_first_thread{
      [](std::promise<T> p, std::shared_ptr<WaitFirstData<T>> data) {
        std::unique_lock<std::mutex> lk(data->res_mutex);
        data->res_cv.wait(lk, [&]() { return data->res_ready; });

        p.set_value(data->res);
      },
      std::move(wait_first_promise), data};

  auto thread_lambda = [](std::future<T> f,
                          std::shared_ptr<WaitFirstData<T>> data) {
    T r = f.get();

    std::lock_guard<std::mutex> lk(data->res_mutex);
    if (!data->res_ready) {
      data->res_ready = true;
      data->res = r;
      data->res_cv.notify_one();
    }
  };

  std::thread t1{thread_lambda, std::move(f1), data},
      t2{thread_lambda, std::move(f2), data};

  wait_first_thread.detach();
  t1.detach();
  t2.detach();

  return wait_first_future;
}

// arbitrary function denoting a potentially long-running task
int func(int n) {
  sleep(n);
  return n;
}

int main() {
  auto f1 = std::async(std::launch::async, func, 2);
  auto f2 = std::async(std::launch::async, func, 15);

  // f3 is resolved as soon as either f1 or f2 completes (with the same return
  // value too)
  auto f3 = wait_first(std::move(f1), std::move(f2));

  // wait for f3 to resolve
  int l = f3.get();

  // computation (here I/O) done after the first among f1 and f2 is completed
  std::cout << "Task done after " << l << " seconds" << std::endl;

  return 0;
}

【讨论】:

    【解决方案2】:

    如果你的计算是顺序的(一个依赖于另一个),你可以使用std::experimental::future方法then,如果你可以访问std::experimental::future(并且不怕使用“实验”)

    您也可以立即将一个未来作为参数传递给第二个计算。 future::get 是等待未来完成的一种方式。如果您需要将第一个未来传递给其他地方,请使用std::shared_future

    【讨论】:

      猜你喜欢
      • 2015-01-07
      • 1970-01-01
      • 2019-10-08
      • 2020-08-05
      • 1970-01-01
      • 2019-01-16
      • 2015-07-05
      • 2019-04-20
      • 2020-07-02
      相关资源
      最近更新 更多