首页 > 解决方案 > 为什么 std::future 从 std::packaged_task 和 std::async 返回不同?

问题描述

我知道了future返回 from的原因std::async有一些特殊的共享状态,通过这种共享状态wait on returned future发生在未来的析构函数中。但是当我们使用 时std::pakaged_task,它的未来不会表现出相同的行为。要完成打包任务,您必须显式调用get()来自 的future对象packaged_task

现在我的问题是:

  1. std::async未来(thinking vs )的内部实现可能是什么std::packaged_task
  2. 为什么相同的行为不适用于future返回 from std::packaged_task?或者,换句话说,如何停止相同的行为std::packaged_task future

要查看上下文,请参阅以下代码:

它不等待完成countdown任务。但是,如果我取消评论// int value = ret.get();,它将完成countdown并且很明显,因为我们实际上是在阻止返回的未来。

    // packaged_task example
#include <iostream>     // std::cout
#include <future>       // std::packaged_task, std::future
#include <chrono>       // std::chrono::seconds
#include <thread>       // std::thread, std::this_thread::sleep_for

// count down taking a second for each value:
int countdown (int from, int to) {
  for (int i=from; i!=to; --i) {
    std::cout << i << std::endl;
    std::this_thread::sleep_for(std::chrono::seconds(1));
  }
  std::cout << "Lift off!" <<std::endl;
  return from-to;
}

int main ()
{
   std::cout << "Start " << std::endl;
  std::packaged_task<int(int,int)> tsk (countdown);   // set up packaged_task
  std::future<int> ret = tsk.get_future();            // get future

  std::thread th (std::move(tsk),10,0);   // spawn thread to count down from 10 to 0

//   int value = ret.get();                  // wait for the task to finish and get result

  std::cout << "The countdown lasted for " << std::endl;//<< value << " seconds.\n";

  th.detach();   

  return 0;
}

如果我使用在另一个线程上std::async执行任务countdown,无论我是否get()返回的future对象上使用,它都会完成任务。

// packaged_task example
#include <iostream>     // std::cout
#include <future>       // std::packaged_task, std::future
#include <chrono>       // std::chrono::seconds
#include <thread>       // std::thread, std::this_thread::sleep_for

    // count down taking a second for each value:
    int countdown (int from, int to) {
      for (int i=from; i!=to; --i) {
        std::cout << i << std::endl;
        std::this_thread::sleep_for(std::chrono::seconds(1));
      }
      std::cout << "Lift off!" <<std::endl;
      return from-to;
    }
    
    int main ()
    {
       std::cout << "Start " << std::endl;
      std::packaged_task<int(int,int)> tsk (countdown);   // set up packaged_task
      std::future<int> ret = tsk.get_future();            // get future
    
      auto fut = std::async(std::move(tsk), 10, 0);   

    
    //   int value = fut.get();                  // wait for the task to finish and get result
    
      std::cout << "The countdown lasted for " << std::endl;//<< value << " seconds.\n";

      return 0;
    }

标签: c++c++14stdasyncpackaged-taskstd-future

解决方案


std::async对如何以及在何处执行给定的任务有明确的了解。这就是它的工作:执行任务。要做到这一点,它必须把它放在某个地方。那个地方可能是一个线程池,一个新创建的线程,或者在一个由谁破坏future.

因为async知道函数将如何执行,所以它拥有 100% 的信息来构建一种机制,该机制可以在潜在的异步执行结束时进行通信,并确保如果你破坏future了执行该函数最终将绕过实际执行它。毕竟,它知道那个机制是什么。

packaged_task 没有。所做的只是存储一个packaged_task可以使用给定参数调用的可调用对象,使用promise函数返回值的类型创建 a,并提供一种获取 afuture和执行生成该值的函数的方法。

任务实际执行的时间和地点无关紧要packaged_taskfuture如果没有这些知识,就无法构建使 's 析构函数与任务同步所需的同步。

假设您想在新创建的线程上执行任务。好的,因此要将其执行与future' 销毁同步,您需要一个互斥锁,析构函数将阻塞该互斥锁,直到任务线程完成。

future但是,如果您想在与析构函数的调用者相同的线程中执行任务怎么办?好吧,那么您不能使用互斥锁来同步它,因为它们都在同一个线程上。相反,您需要让析构函数调用该任务。这是一个完全不同的机制,它取决于你计划如何执行。

因为packaged_task不知道您打算如何执行它,所以它无法执行任何操作。

请注意,这不是packaged_task. 从用户创建的对象创建的所有 s都不会具有s的特殊属性。futurepromiseasyncfuture

所以问题真的应该是为什么这样async工作,而不是为什么其他人不这样做

如果您想知道这一点,那是因为两个相互竞争的需求:async需要是一种高级的、脑死亡的简单方法来获得异步执行(破坏时同步是有意义的),并且没有人想创建一个新的future除了析构函数的行为外,它与现有的类型相同。所以他们决定重载如何future工作,使其实现和使用复杂化。


推荐阅读