c++ 何时使用std::async与std::threads?

mm9b1k5b  于 2023-03-20  发布在  其他
关注(0)|答案(6)|浏览(210)

有人能给予一个关于何时使用它们的高层次直觉吗?
参考文献:

liwlm1x9

liwlm1x91#

这并不是一个非此即彼的事情--你可以在手工创建的std::线程中使用futures(和promise一起)。使用std::async是一个方便的方法,可以为一些异步计算启动一个线程,并通过future将结果封送回来,但是std::async在当前的标准中受到相当大的限制。如果建议的扩展能够结合微软的PPL的一些想法,它将变得更加有用。
目前,std::async可能最适合处理非常长时间运行的计算或相当简单的程序的长时间运行的IO。(并且实际上它被指定的方式使得难以在幕后用线程池来实现),因此它不太适合更细粒度的工作负载,为此您需要使用std::thread滚动自己的线程池,或者使用Microsoft的PPL或Intel的TBB。
您还可以使用std::thread来编写“传统”POSIX线程风格的代码,而这种代码是以一种更现代、更便携的方式编写的。
BartoszMilewski在他的文章Async Tasks in C++11: Not Quite There Yet中讨论了当前指定std::async方式的一些限制

olqngx59

olqngx592#

我发现的一个简单原因是,当你需要一种方法来检测(通过轮询)异步作业是否完成时,使用std::thread,你必须自己管理它,使用std::async,你可以查询std::future::valid()(或者使用std::future::wait_for/wait_until(...))来知道它何时完成。

0ejtzxu1

0ejtzxu13#

std::thread上使用std::future的一个用例是你想调用一个返回值的函数。当你想返回函数的值时,你可以调用get()方法。
std::thread不提供获取函数返回值的直接方法。

e5nqia27

e5nqia274#

我意识到这个问题已经提出8年了。C++并发领域已经发生了很大的变化。最近我也不得不在这个领域徘徊,想知道前进的道路。我想分享我的一些想法,并可能得到验证。我会稍微修改原来的问题为std::async vs thread pool,而不仅仅是std::thread。
从2011年起,我就开始大量使用boost::thread_group和boost::asio::io_service分别用于线程池和事件循环。

int noOfCores = boost::thread::hardware_concurrency();
for (int i = 0; i < noOfCores; i++)
{
    _threadPool.create_thread(boost::bind(&pri_queue::run, &_taskQueue));
}

任务queue _taskQueue的类型为pri_queue,与boost example有点类似,不同之处在于run()函数等待io_service.run_one(),因此,我还通过在排队时分配优先级来控制任务执行的优先级。
在这之后,我可以使用post()在这个队列中抛出任何函数(使用boost::bind与参数绑定)来执行,或者使用boost::asio::deadline_timer::async_wait()来安排它的延迟。
由于我的框架中的所有东西都是事件驱动的,我很乐意在等待boost example of async http client中的事件时将任何功能划分为多个函数对象。这个模型经过了时间测试,没有线程创建成本,因为每个线程都是预先创建的。
然而,自从我在公司的所有产品中采用这种模型以来,C标准已经更新了3次(14、17、20)。所以当我看到所有新的变化时,你可以说我有点FOMO。对不起,在看了std::async & coroutines之后,我看不出它们是如何帮助像我这样已经习惯使用io_service +线程池模型的人的。它看起来更昂贵,而且我无法控制优先级或线程创建,implementation differs是所有编译器的。
我看到它使函数看起来是同步的和结构化的(所有部分都在一个地方),而不是将异步功能拆分成多个函数对象。
对于C
老手来说,我会说线程池比std::async甚至协程都要好,当然,如果应用程序不是事件驱动的,或者你是异步编程的新手,std::async会更容易处理。

cnjp1d6j

cnjp1d6j5#

我认为std::async/std::future相对于一般std::thread方法的一个巨大优势是免费的异常传播。如果你的线程函数抛出,std::future::get就会抛出。这个特性真的很方便。这个特性与处理返回值非常相似(rg665n在另一个答案中提到)。
如果你使用std::thread,你需要创建一个类,它包含返回值的成员和可能发生的异常。你需要为所有这些成员添加互斥锁。通常,如果你使用std::thread而不是std::async来完成简单的任务,你的代码会长出几百行。
我对你的问题的回答是:如果你的子任务之间没有相互干扰,使用std::async/std::future。如果你必须在你的子任务之间同步状态,使用std::thread(因为它们相互依赖)。对于观察者模式或线程,使用std::thread,这将运行整个程序生命周期。

az31mfrm

az31mfrm6#

除了其他伟大的答案,它可能是值得阅读的第35项从有效的现代CPP书从斯科特迈耶赞成std::异步超过std::线程。
从那本书中引用以下文字。
最先进的线程调度器使用系统范围的线程池来避免超额订阅,并通过工作窃取算法来改善硬件内核之间的负载平衡。C标准不要求使用线程池或工作窃取,而且,老实说,C11并发规范的一些技术方面使得使用它们比我们希望的要困难。一些供应商在他们的标准库实现中利用了这种技术,并且可以合理地预期在这一领域会继续取得进展。如果你在并发编程中采用基于任务的方法,随着这种技术的广泛应用,你会自动获得这种技术的好处。另一方面,如果你直接使用std::threads编程,你自己就承担了处理线程耗尽、超额预订和负载平衡的负担,更不用说你对这些问题的解决方案如何与在同一台机器上的其他进程中运行的程序中实现的解决方案相结合了

相关问题