Item 35: Prefer task-based programming to thread-based.

Item 35: Prefer task-based programming to thread-based.

如果你想异步运行一个函数 donAsyncWork,你有两个基本的选择:基于线程的方法(thread-based)和基于任务的方法(task-based)。

int doAsyncWork();
std::thread t(doAsyncWork);         // thread-based
auto fut = std::async(doAsyncWork); // task-based

在比较二者优劣前,我们先介绍下 C++ 软件中线程的3个层次:

  • 硬件线程。硬件实际执行计算的并行数。现代计算机架构中,一个硬件核对应一个或多个硬件线程。
  • 软件线程。也被称为系统线程,指的是操作系统管理核调度的所有线程。软件线程运行在硬件线程之上,并且可以创建的软件线程要多于硬件线程。这样的好处是:当某些软件线程处于阻塞状态(等待IO、mutex、condition variable)时,可以执行其他线程以提高吞吐率。
  • std::thread。C++ 的线程对象,作为句柄对应系统的软件线程。std::thread 也可以是空句柄而不对应系统的软件线程。例如没有执行函数、执行函数被移动其他线程、已经 joindetachedstd::thread 对象。

基于任务的方法一般要优于基于线程的方法。

doAsyncWork 有返回值,可以代表任务的执行状态。基于线程的方法没有提供一个很好的机制获取返回值。而 std::async 返回的 std::future 对象提供了 get 方法可以获取到返回值。并且当 doAsyncWork 返回异常时,基于线程的方法直接抛出 std::terminate,而基于任务的方法可以根据返回值做异常处理。

系统的软件线程是有限的,当请求创建的 std::thread 多于系统提供的最大软件线程数,将抛出 std::system_error,即使 doAsyncWork 被设置成 noexcept。因而基于线程的方法需要处理这种情况,这就需要对线程进行管理。

即使你没有用尽软件线程,基于线程的方法还存在认购超额(oversubscription)的问题,即就绪态的软件线程高于硬件线程。操作系统会采用时间片轮询的方式执行所有的软件线程,而线程的上下文切换会增加线程管理的开销。并且硬件线程被切换到另一个软件线程时,其 cache 上的数据通常会失效,也会增加线程的开销。想要避免认购超额问题还比较困难,软件线程于硬件线程的合理比例取决于多种因素。例如硬件架构的特点、cache的使用方式、任务的特点等。

综上,线程的管理是比较困难的。而基于任务的方法将线程管理交给了 C++ 标准库,而 C++ 标准库可以更好地管理线程。例如,你无需担心软件线程耗尽的问题,因为默认参数的 std::async 不一定会创建线程,它可能在认购超额时将当前任务安排在当前线程上执行。另外 C++ 标准库可能比你更清楚硬件线程的资源,可以很好的避免负载不均衡的问题。

当然,基于线程的方法也有一定的优势:

  • 需要访问实现线程的底层API。std::thread 可以获取底层线程的句柄,可以使用底层线程的API。
  • 需要优化线程的使用。例如,如果你正在开发一个服务软件,而这个软件是这台机器上执行的唯一有意义的进程,并且你清楚这台机器的硬件配置。
  • 需要实现一些高级的线程技术。例如线程池技术,而 C++ 标准库没有提供。

除了上述情况外,建议优先使用基于任务的编程方法。

参考:

  • 5
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值