微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

为什么std :: future与std :: packaged_task和std :: async返回的不同? 情况1 — std::function不足以在不同的线程中执行操作情况2 — std::packaged_task解决了std::packaged_task无法解决的问题情况3:std::packaged_task完全不同

如何解决为什么std :: future与std :: packaged_task和std :: async返回的不同? 情况1 — std::function不足以在不同的线程中执行操作情况2 — std::packaged_task解决了std::packaged_task无法解决的问题情况3:std::packaged_task完全不同

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

现在我的问题是:

  1. 未来的内部实现是什么(考虑packaged_taskstd::async)?
  2. 为什么对std::packaged_task返回的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

解决方法

std::async对执行给定任务的方式和位置有明确的了解。那就是它的工作:执行任务。为此,它必须实际放置在某个地方。某个地方可能是线程池,新创建的线程,也可能是销毁future的任何人执行的地方。

由于async知道该函数将如何执行,因此它拥有100%的信息,以建立一种机制,该机制可以在潜在的异步执行结束时进行通信,并确保您可以销毁该函数future,那么将要执行该功能的任何机制最终都将绕过实际执行它的过程。毕竟,它知道该机制是什么。

但是packaged_task 不是packaged_task所做的全部工作就是存储一个可调用对象,该对象可以使用给定的参数进行调用,使用函数的返回值类型创建一个promise,并提供一种同时获取future的方式并执行生成值的函数。

实际执行任务的时间和地点与packaged_task无关。没有这些知识,就无法建立使future的析构函数与任务同步所需的同步。

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

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

由于packaged_task不知道您打算如何执行它,因此它无法执行任何操作。

请注意,这并非packaged_task所独有。从用户创建的future对象创建的所有 promise个将不具有asyncfuture的特殊属性。

所以问题实际上应该是async为什么这样工作,而不是为什么其他所有人都不

如果您想知道,这是因为两个相互竞争的需求:async必须是获得异步执行的高级,脑筋急转弯的简单方法(对于这种情况,销毁同步是有意义的) ,没有人愿意创建一个新的future类型,除了其析构函数的行为外,该类型与现有类型相同。因此,他们决定超载future的工作方式,使其实现和使用变得复杂。

,

@Nicol Bolas非常满意地提出了already answered这个问题。因此,我将尝试从不同角度稍微回答这个问题,阐述@Nicol Bolas已经提到的要点。

相关事物的设计及其目标

考虑我们要通过各种方式执行的简单函数:

int add(int a,int b) {
    std::cout << "adding: " << a << ","<< b << std::endl;
    return a + b;
}

暂时忘记std::packaged_taskstd ::futurestd::async,让我们退后一步,回顾一下std::function的工作原理和问题原因。

情况1 — std::function不足以在不同的线程中执行操作

std::function<int(int,int)> f { add };

一旦有了f,我们就可以在同一线程中执行它,例如:

int result = f(1,2); //note we can get the result here

或者,在另一个线程中,如下所示:

std::thread t { std::move(f),3,4 };
t.join(); 

如果我们仔细观察,就会意识到在另一个线程中执行f会产生一个新问题:我们如何获得函数的结果?相同的线程没有这个问题-我们将结果作为返回值来获取,但是在不同的线程中执行时,我们没有任何方法来获取结果。这正是f解决的问题。

情况2 — std::packaged_task解决了std::packaged_task无法解决的问题

尤其是,它在线程之间创建一个通道,以将结果发送到另一个线程。除此之外,它与std::function大致相同。

std::function

现在您将看到std::packaged_task<int(int,int)> f { add }; // almost same as before std::future<int> channel = f.get_future(); // get the channel std::thread t{ std::move(f),30,40 }; // same as before t.join(); // same as before int result = channel.get(); // problem solved: get the result from the channel 如何解决std::packaged_task所产生的问题。但是,这并不意味着必须std::function必须在其他线程中执行。您也可以像std::packaged_task一样在同一线程中执行它,尽管您仍然会从通道中获得结果。

std::function

因此,std::packaged_task<int(int,int)> f { add }; // same as before std::future<int> channel = f.get_future(); // same as before f(10,20); // execute it in the current thread !! int result = channel.get(); // same as before std::function基本上是类似的东西:它们只是包装可调用实体,但有一个区别:std::packaged_task是多线程友好的,因为它提供了一个可通过其进行访问的通道可以将结果传递给其他线程。他们两个都不自己执行包装的可调用实体。一个人需要在同一线程或另一个线程中调用来执行包装的可调用实体。因此,在这个空间中基本上有两种东西:

  • 执行什么,即常规功能std::packaged_taskstd::function
  • 执行方式/位置,即线程,线程池,执行程序等。

情况3:std::packaged_task完全不同

这是另一回事,因为它将执行的内容执行方式/执行位置相结合。

std::async

请注意,在这种情况下,创建的future具有关联的执行程序,这意味着future将在某个时刻完成,因为有人在幕后执行事物。但是,在std::future<int> fut = std::async(add,100,200); int result = fut.get(); 创建的将来的情况下,不一定有执行者,并且如果将创建的任务从不分配给任何执行者,则将来可能永远无法完成。

希望可以帮助您了解事物在幕后的工作方式。参见the online demo

两种std::packaged_task

之间的区别

现在,很明显可以创建两种std::future

  • std::future可以创建一种。这样的未来有一个相关的执行者,因此可以完成。
  • 可以通过std::async或类似的方式创建其他种类。这样的未来不一定具有相关的执行者,因此可能会也可能不会完成。

因为在第二种情况下,将来不一定有关联的执行程序,所以其析构函数不是为完成/等待而设计的,因为它可能永远不会完成:

std::packaged_task

希望这个答案可以帮助您从不同的角度理解事物。

,

行为上的变化是由于std::threadstd::async之间的差异所致。

在第一个示例中,您通过分离创建了守护线程。在主线程中打印std::cout << "The countdown lasted for " << std::endl;的位置可能发生在countdown线程函数内的print语句之前,之中或之后。由于主线程不会等待生成的线程,因此您甚至可能看不到所有打印输出。

在第二个示例中,您使用std::launch::deferred策略启动线程功能。 behaviour for std::async是:

如果选择了异步策略,则关联的线程完成将与等待共享状态的第一个函数的成功返回同步,或者与释放共享状态的最后一个函数的同步返回同步 strong>,以先到者为准。

在此示例中,您具有相同共享状态的两个期货。在退出main时调用其dtor之前,必须完成异步任务。即使您没有明确定义任何期货,被创建和销毁的临时期货(从调用std::async返回)也将意味着任务在主线程退出之前完成。


Here是Scott Meyers的精彩博客文章,阐明了std::futurestd::async的行为。

Related SO post

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。