为什么要使用std :: async?


70

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

  • 它称为async,但它确实具有“顺序行为”,基本上在您调用与async函数foo相关联的future的行中,该程序将阻塞直到foo执行完毕。
  • 它取决于与其他库完全相同的外部库以及更好的非阻塞解决方案,这意味着pthread,如果要使用std::async,则需要pthread。

在这一点上,我很自然地问为什么甚至在一组简单的函子上都选择std :: async?该解决方案甚至根本无法扩展,您调用的未来越多,程序的响应性就越差。

我想念什么吗?您能否显示一个示例,该示例被授予以异步,非阻塞方式执行?


@rsaxvc在您调用异步函数,例如future.get()
user2485710

1
您的假设是错误的。async()旨在提供一个同步点,因此您可以获得异步评估函数的结果。
DanielKO

与其他选项相比,C ++当前的“异步”思想并没有给表带来任何重要的意义(除了可移植性之外)。一旦它获得了完成时继续支持(这几乎是所有其他平台称为“异步”的重要组成部分),我怀疑您会发现它有更多用途。
科里·纳尔逊

这说明异步如何使得获取返回值超级简单:stackoverflow.com/questions/7686939/...
西罗桑蒂利郝海东冠状病六四事件法轮功

Answers:


61

如果需要异步操作的结果,则无论使用什么库,必须阻塞。这样做的想法是,您可以选择何时阻止,并且希望在执行此操作时,您可以忽略不计的时间,因为所有工作都已经完成。

另请注意,std::async可以使用策略std::launch::async或启动std::launch::deferred。如果不指定,则允许选择实现,并且它很可能选择使用递延评估,当您尝试从将来获取结果时,这将导致所有工作都已完成,从而导致更长的时间。因此,如果您要确保工作是异步完成的,请使用std::launch::async


1
@ user2485710,如果在启动线程中需要结果,在检索结果时需要阻塞。如果结果尚未准备好,它将无法使用结果。因此,如果要获得结果,则必须等到准备就绪。如果已经准备好,则阻塞时间可以忽略不计。
juanchopanza

2
根据我的经验,@ user2485710还应注意std::async具有预期的异步行为。
juanchopanza


1
@chill阻止以返回结果。就像任何同步功能一样。从未来获取是一个同步点。
juanchopanza

2
@juanchopanza,好吧,不要将其转变为聊天,您说的是绝对没有道理的,在进程/线程执行的上下文中的“阻塞”具有公认的语义,可以将执行从“运行”中分离出来处于“等待”状态,这两个都不一定在这里发生。过度。
2013年

81
  • 它称为异步,但它确实具有“顺序行为”,

不,如果您使用该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();

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


3
很好的答案!我对最后一个示例有疑问:尽管您可以强制将doSomethingThatTakesTenSeconds其在单独的线程中启动,但是您不能强制其“立即”运行,对吗?如果真是这样,那么“ fut.get()”仍然可能会阻塞。
GuLearn 2013年

18
是的,但是如果要花很长时间启动新线程,则您的系统将非常过载,或者其调度程序很垃圾。
Jonathan Wakely 2013年

1
我在coliru测得的延迟大约为100µs至300µs。大约33毫秒的最大值是好的。我很想知道该代码在同一盒上的Windows VS vs linux g ++上做什么。
doug65536 '16

1
实施如何决定政策?例如,我想std::launch::deferred什么时候计算比线程调度便宜(它可能会进入内核)?(对不起,我不熟悉,std::thread并假定它是由操作系统安排的。)
Franklin Yu

2
“除非在Windows上使用GCC,否则在Windows上它不会使用ConcRT功能”,在这种情况下,它可能会使用WinPthreads或类似的软件
uLoop,

16

我认为您的问题是std::future说它继续存在get。仅在结果尚未准备好时才阻塞。

如果您可以安排结果已经准备好,那么这不是问题。

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

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

例如,想象一下并行递归合并排序。它将数组分为两个块,然后async对一个块进行排序,同时对另一个块进行排序。一旦完成对一半的排序,在第二个任务完成之前,发起线程无法继续进行。因此,它会执行.get()和阻止。将两个半部分都排序后,便可以进行合并(理论上,合并也可以至少部分并行进行)。

对于那些在外部与之交互的人员而言,此任务的行为就像是线性任务-完成后,对数组进行排序。

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


3

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

如果设置了异步标志(即policy&std :: launch :: async!= 0),那么异步将在单独的执行线程上执行函数f,就像由std :: thread(f,args ...)产生一样,除了函数f返回值或引发异常外,它存储在可通过std :: future访问的共享状态中,异步返回给调用者

记录引发的异常是一个很好的属性。


这不是保证,根据标准,我看不到为什么给定的C ++ 11实现被迫使用与“常规”单线程执行不同的东西。
user2485710

2
@ user2485710之所以被强制,是因为该标准这样说:“就像在新的执行线程中一样”(第30.6.8 / 3节)。
R. Martinho Fernandes

1
@ user2485710:和“好像在新的执行线程中”具有可观察到的效果,例如线程局部变量。当然,如果编译器可以证明它是不可观察的,则可以在视情况规则下自由地同步执行它,但这是实现质量的问题。
JohannesD

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.