【问题标题】:When to use std::async vs std::threads?何时使用 std::async 与 std::threads?
【发布时间】:2023-03-05 13:56:01
【问题描述】:

【问题讨论】:

  • 使用您知道如何应用于您的问题的任何一个。有些问题可以更好地映射到其中一个,这自然会反映在您的思考中。
  • 但是它们是等价的吗?我不这么认为。 std::async 只是为了让生活更轻松还是为了解决以前的 C++ 问题?
  • 有趣的阅读(关于std::async)如果使用g++:async(f) isn't.,还有this
  • @JaviV:他们在非常不同的抽象级别上解决了相同的问题(并发)。
  • @JaviV 它们大多是不相关的问题(延迟异步甚至不会在与等待它的线程不同的线程上执行)。它们有时在概念上大部分是等效的(例如,启动一些任务并立即获得相应的期货或加入相应的线程)。有时它们是互补的(例如,创建许多延迟异步,其中关联的期货在线程池上执行)。有时这两个概念中只有一个与问题相关(例如,线程仅用作监控或 GUI 线程;延迟异步仅用于实现延迟评估)

标签: c++ multithreading c++11 stdasync


【解决方案1】:

这并不是一个真正的非此即彼的事情——你可以使用futures(连同promises)和手动创建的std::threads。使用std::async 是一种方便的方式来触发线程进行一些异步计算并通过未来将结果编组回来,但std::async 在当前标准中相当有限。如果采纳微软 PPL 中包含一些想法的建议扩展,它将变得更加有用。

目前,std::async 可能最适合处理相当简单的程序的长时间运行的计算或长时间运行的 IO。虽然它并不能保证低开销(实际上它的指定方式使得在后台使用线程池难以实现),因此它不太适合更细粒度的工作负载。为此,您要么需要使用 std::thread 滚动自己的线程池,要么使用 Microsoft 的 PPL 或 Intel 的 TBB 之类的东西。

您还可以将std::thread 用于以更现代和可移植的方式编写的“传统”POSIX 线程样式代码。

Bartosz Milewski 在他的文章Async Tasks in C++11: Not Quite There Yet 中讨论了std::async 当前指定方式的一些限制

【讨论】:

  • 所以如果我想编写高效的代码(或者线程被频繁创建和加入),最好还是坚持经典线程?
  • 为了高效的代码,你要避免频繁地创建和加入线程 :) 这就是 std::async 的问题——它很可能通过触发一个新线程而不是触发一个新线程来实现带有线程池。如果您想要高效的细粒度线程工作负载,您通常需要线程池之类的东西。
  • @mattnewport:实际上,std::async most often does use a threadpool。但这不是必须的。
  • 尽管微软没有通过使用线程池来遵循标准。我在答案末尾添加了链接的文章讨论了一些原因。微软正在推动对 std::future 的扩展,以使其在标准的未来修订版中与基于线程池/任务的并行方法更加兼容。
  • 以下是 Bjarne Stroustroup 对异步的评价:......甚至不要考虑使用 async() 来启动执行 I/O、操作互斥锁或以其他方式与其他任务交互的任务. async() 背后的思想与 range-for 语句背后的思想相同:提供一种简单的方法来处理最简单、相当常见的情况,并将更复杂的示例留给完全通用的机制。
【解决方案2】:

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

【讨论】:

  • 如果您使用std::threadstd::packaged_task,则可以进行轮询。
【解决方案3】:

std::thread 上使用std::futrue 的一个用例是您要调用一个返回值的函数。当你想要函数的返回值时,你可以调用get()future方法。

std::thread 没有提供直接获取函数返回值的方法。

【讨论】:

    【解决方案4】:

    我知道这个问题提出已经 8 年了。从那时起,C++ 并发环境发生了很大变化。最近我也不得不在这片风景中徘徊,不知道该走哪条路才能前进。我想分享我的一些想法,并可能得到验证。我会将原始问题稍微修改为 std::async 与线程池,而不仅仅是 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));
    }
    

    任务队列 _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 和协程之后,我看不出它们如何帮助像我这样已经习惯使用 io_service + 线程池模型的人。它看起来更昂贵,而且我无法控制优先级或线程创建,implementation differs 跨编译器。

    我发现它使函数看起来是同步的和结构化的(所有部分都在一个地方),与异步功能溢出到多个函数对象相比。

    对于 C++ 的老手,我会说线程池比 std::async 甚至协程更好。当然,如果应用程序不是事件驱动的,或者如果您不熟悉异步编程,std::async 会更容易处理。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-04-01
      • 2015-08-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-04-21
      • 1970-01-01
      相关资源
      最近更新 更多