为什么我应该使用std::async

Why should I use std::async?

本文关键字:async std 我应该 为什么      更新时间:2023-10-16

我试图深入探索新C++11标准的所有选项,在使用std::async并阅读其定义时,我注意到了两件事,至少在带有gcc 4.8.1:的linux下是这样

  • 它被称为async,但它有一个真正的"顺序行为",基本上在你调用与异步函数foo相关的future的那一行,程序会阻塞,直到foo执行完成
  • 它依赖于与其他库完全相同的外部库,以及更好的非阻塞解决方案,即pthread,如果您想使用std::async,则需要pthread

在这一点上,我很自然地会问,为什么选择std::async而不是一组简单的函数?这是一个根本不可扩展的解决方案,你调用的未来越多,你的程序响应就越差。

我是不是错过了什么?你能展示一个被允许以异步、非阻塞的方式执行的例子吗?

  • 它被称为async,但它有一个真正的"顺序行为"

不,如果使用std::launch::async策略,那么它将在新线程中异步运行。如果不指定策略,则可能会在新线程中运行。

基本上,在调用与异步函数foo相关联的future的行中,程序会阻塞,直到执行完foo为止。

它只在foo还没有完成的情况下阻止,但如果它是异步运行的(例如,因为您使用了std::launch::async策略),它可能在您需要它之前就已经完成了

  • 它依赖于与其他库完全相同的外部库,以及更好的非阻塞解决方案,这意味着pthread,如果你想使用std::async,你需要pthread

错了,它不必使用Pthreads来实现(在Windows上也不是,它使用了ConcRT功能。)

在这一点上,我很自然地会问,为什么选择std::async而不是一组简单的函数?

因为它保证了线程安全并在线程之间传播异常。你能用一组简单的函子做到这一点吗?

这是一个根本不可扩展的解决方案,你调用的未来越多,你的程序响应就越差。

不一定。如果您没有指定启动策略,那么智能实现可以决定是启动新线程,还是返回延迟函数,或者返回稍后决定何时可以使用更多资源的内容。

现在,对于GCC的实现,如果您不提供启动策略,那么对于当前版本,它将永远不会在新线程中运行(有一个bugzilla报告),但这是该实现的属性,而不是std::async的属性。您不应该将标准中的规范与特定的实现混淆。阅读一个标准库的实现是学习C++11的一种糟糕的方式。

你能展示一个被允许以异步、非阻塞的方式执行的例子吗?

这不应该阻止:

auto fut = std::async(std::launch::async, doSomethingThatTakesTenSeconds);
auto result1 = doSomethingThatTakesTwentySeconds();
auto result2 = fut.get();

通过指定启动策略,您可以强制异步执行,如果您在执行过程中做其他工作,则结果将在需要时准备好。

如果您需要异步操作的结果,则无论您使用什么库,都有要阻止。这个想法是,你可以选择何时阻止,希望当你这样做的时候,你阻止的时间可以忽略不计,因为所有的工作都已经完成了。

还要注意,std::async可以通过策略std::launch::asyncstd::launch::deferred来启动。如果您不指定它,则允许实现进行选择,并且它很可能选择使用延迟评估,这将导致在您试图从未来获得结果时完成所有工作,从而导致更长的块。因此,如果您想确保工作是异步完成的,请使用std::launch::async

我认为您的问题是std::future说它阻塞了get。只有当结果还没有准备好时,它才会阻止

如果你能安排结果已经准备好,这不是问题。

有很多方法可以知道结果已经准备好了。你可以轮询future并询问它(相对简单),你可以使用锁或原子数据来传递它已经准备好的事实,你可以建立一个框架来将"完成的"future项目交付到消费者可以交互的队列中,你可以用某种信号(这只是一次阻塞多个项目,或者轮询)。

或者,你可以在本地完成所有可以做的工作,然后阻止远程工作。

举个例子,想象一个并行递归合并排序。它将数组拆分为两个块,然后在一个块上执行async排序,同时对另一个块进行排序。一旦完成了对其一半的排序,起始线程就无法继续,直到第二个任务完成。所以它做了一个.get()和块。一旦对两半都进行了排序,它就可以进行合并(理论上,合并也可以至少部分并行进行)。

对于那些在外部与它交互的人来说,这个任务的行为就像一个线性任务——当它完成时,数组就会被排序。

然后,我们可以将其封装在std::async任务中,并获得一个future排序的数组。如果我们愿意,我们可以添加一个信号过程,让我们知道future已经完成,但只有当我们有一个线程在等待信号时,这才有意义。

在参考中:http://en.cppreference.com/w/cpp/thread/async

如果设置了async标志(即policy&std::launch::async!=0),则async在单独的执行线程上执行函数f,就好像由std::thread(f,args…)派生,除了如果函数f返回值或引发异常,该值存储在共享通过std::future可访问的状态,async返回到调用者

记录抛出的异常是一个不错的属性。

http://www.cplusplus.com/reference/future/async/

有三种类型的策略,

  1. launch::async
  2. launch::deferred
  3. launch::async|launch::deferred

则默认情况下CCD_ 22被传递给CCD_。