为什么 std::future 与 std::packaged_task 和 std::async 返回的不同?

2024-02-08

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

现在我的问题是:

  1. 未来的内部实现可能是什么(思考std::async vs std::packaged_task)?
  2. 为什么同样的行为没有应用于future从返回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对象或not,它总会完成任务。

// 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;
    }

std::async明确了解所分配任务的执行方式和地点。这就是它的工作:execute任务。为此,它必须将其实际放置在某个地方。这个地方可能是一个线程池、一个新创建的线程,或者是由破坏该线程的人执行的地方。future.

Because async知道该函数将如何执行,它拥有构建一种机制所需的 100% 信息,该机制可以在潜在的异步执行结束时进行通信,并确保如果您销毁该函数future,那么无论哪种机制将执行该函数,最终都会真正执行它。毕竟,它知道那个机制是什么。

But packaged_task doesn't. All packaged_task的作用是存储一个可调用对象,可以使用给定的参数调用该对象,创建一个promise与函数返回值的类型,并提供一种方法来获取future并执行生成该值的函数。

任务实际执行的时间和地点都不是packaged_task的事。如果没有这些知识,就需要进行同步future与任务同步的析构函数根本无法构建。

假设您想在新创建的线程上执行任务。好的,所以要与它的执行同步future的析构,您需要一个互斥锁,析构函数将阻止该互斥锁,直到任务线程完成。

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

Because packaged_task不知道你打算如何执行它,它不能做任何事情。

请注意,这并不是唯一的packaged_task. All futures 从用户创建的promise对象不会具有以下特殊属性async's futures.

所以问题应该是为什么async就是这样工作的,而不是为什么其他人doesn't.

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

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

为什么 std::future 与 std::packaged_task 和 std::async 返回的不同? 的相关文章

随机推荐