packaged_task和async有什么区别

nij*_*sen 120 c++ multithreading packaged-task c++11 stdasync

在使用C++ 11的线程模型时,我注意到了这一点

std::packaged_task<int(int,int)> task([](int a, int b) { return a + b; });
auto f = task.get_future();
task(2,3);
std::cout << f.get() << '\n';
Run Code Online (Sandbox Code Playgroud)

auto f = std::async(std::launch::async, 
    [](int a, int b) { return a + b; }, 2, 3);
std::cout << f.get() << '\n';
Run Code Online (Sandbox Code Playgroud)

似乎做了完全相同的事情.据我所知,有可能是一个重大的区别,如果我跑std::asyncstd::launch::deferred,但有一个在这种情况下?

这两种方法有什么区别,更重要的是,我应该使用哪种用例?

Zet*_*eta 149

实际上,如果您使用相当长的函数,例如,您刚刚给出的示例显示了差异

//! sleeps for one second and returns 1
auto sleep = [](){
    std::this_thread::sleep_for(std::chrono::seconds(1));
    return 1;
};
Run Code Online (Sandbox Code Playgroud)

打包任务

A packaged_task不会自己启动,你必须调用它:

std::packaged_task<int()> task(sleep);

auto f = task.get_future();
task(); // invoke the function

// You have to wait until task returns. Since task calls sleep
// you will have to wait at least 1 second.
std::cout << "You can see this after 1 second\n";

// However, f.get() will be available, since task has already finished.
std::cout << f.get() << std::endl;
Run Code Online (Sandbox Code Playgroud)

std::async

另一方面,std::asyncwith launch::async将尝试在不同的线程中运行任务:

auto f = std::async(std::launch::async, sleep);
std::cout << "You can see this immediately!\n";

// However, the value of the future will be available after sleep has finished
// so f.get() can block up to 1 second.
std::cout << f.get() << "This will be shown after a second!\n";
Run Code Online (Sandbox Code Playgroud)

退税

但在尝试使用async所有内容之前,请记住返回的未来有一个特殊的共享状态,它要求future::~future阻塞:

std::async(do_work1); // ~future blocks
std::async(do_work2); // ~future blocks

/* output: (assuming that do_work* log their progress)
    do_work1() started;
    do_work1() stopped;
    do_work2() started;
    do_work2() stopped;
*/
Run Code Online (Sandbox Code Playgroud)

因此,如果您想要真正的异步,则需要保留返回的future,或者如果情况发生变化则不关心结果:

{
    auto pizza = std::async(get_pizza);
    /* ... */
    if(need_to_go)
        return;          // ~future will block
    else
       eat(pizza.get());
}   
Run Code Online (Sandbox Code Playgroud)

有关更多信息,请参阅香草萨特的文章async~future,它描述了问题,和Scott Meyer的std::futures距离std::async是不是特别的,它描述了见解.另请注意,此行为是在C++ 14及更高版本中指定的,但也通常在C++ 11中实现.

进一步的差异

通过使用,std::async您无法再在特定线程上运行任务,std::packaged_task可以将其移动到其他线程.

std::packaged_task<int(int,int)> task(...);
auto f = task.get_future();
std::thread myThread(std::move(task),2,3);

std::cout << f.get() << "\n";
Run Code Online (Sandbox Code Playgroud)

此外,packaged_task需要在调用之前调用f.get(),否则程序将冻结,因为将来永远不会准备好:

std::packaged_task<int(int,int)> task(...);
auto f = task.get_future();
std::cout << f.get() << "\n"; // oops!
task(2,3);
Run Code Online (Sandbox Code Playgroud)

TL; DR

使用std::async,如果你想一些事情做,他们就完成了的时候真的不关心,而且std::packaged_task如果你想包裹起来的东西,以便将它们移动到其他线程或以后打电话给他们.或者,引用克里斯蒂安:

最后,a std::packaged_task只是一个较低级别的实现功能std::async(这就是为什么它可以比std::async与其他更低级别的东西一起使用更多的东西,比如std::thread).简单地说a std::packaged_taskstd::function链接到a std::futurestd::asyncwrap并调用std::packaged_task(可能在不同的线程中).

  • 最后,`std :: packaged_task`只是实现`std :: async`的低级功能(这就是为什么它可以比`std :: async`更多,如果与其他更低级别的东西一起使用,比如`的std :: thread`).*简单地说'std :: packaged_task`是一个`std :: function`链接到`std :: future`和`std :: async`包装并调用`std :: packaged_task`(可能在不同的地方)线). (20认同)
  • 你应该添加async块在销毁时返回的未来(就像你调用了get),而从packaged_task返回的那个则没有. (7认同)

Dan*_*ica 10

长话短说

std::packaged_task允许我们获得std::future某个callable的“边界” ,然后控制该 callable 的执行时间和地点,而无需该 future 对象。

std::async启用第一个,但不启用第二个。也就是说,它允许我们获得某些可调用对象的未来,但是如果没有该未来对象,我们就无法控制它的执行。

实际例子

这是一个可以用 解决std::packaged_task但不能用解决的问题的实际示例std::async

考虑您想要实现一个线程池。它由固定数量的工作线程共享队列组成。但是什么是共享队列呢?std::packaged_task这里非常适合。

template <typename T>
class ThreadPool {
public:
  using task_type = std::packaged_task<T()>;

  std::future<T> enqueue(task_type task) {
      // could be passed by reference as well...
      // ...or implemented with perfect forwarding
    std::future<T> res = task.get_future();
    { std::lock_guard<std::mutex> lock(mutex_);
      tasks_.push(std::move(task));
    }
    cv_.notify_one();
    return res;
  }

  void worker() { 
    while (true) {  // supposed to be run forever for simplicity
      task_type task;
      { std::unique_lock<std::mutex> lock(mutex_);
        cv_.wait(lock, [this]{ return !this->tasks_.empty(); });
        task = std::move(tasks_.top());
        tasks_.pop();
      }
      task();
    }
  }
  ... // constructors, destructor,...
private:
  std::vector<std::thread> workers_;
  std::queue<task_type> tasks_;
  std::mutex mutex_;
  std::condition_variable cv_;
};
Run Code Online (Sandbox Code Playgroud)

此类功能无法通过std::async. 我们需要返回一个std::futurefrom enqueue()。如果我们std::async在那里调用(即使使用延迟策略)并返回std::future,那么我们将无法选择如何执行 中的可调用对象worker()。请注意,您不能为同一共享状态创建多个 future(future 是不可复制的)。