C++ 之父的多线程编程建议——现代 C++ 对多线程/并发的支持(下)

2023-05-16

本文承接前文 现代 C++ 对多线程/并发的支持(上),翻译自 C++ 之父 Bjarne Stroustrup 的 C++ 之旅(A Tour of C++)一书的第 13 章 Concurrency。

本文将继续介绍 C++ 并发中的 future/promisepackaged_task 以及 async() 的用法。

目录

  • 13.7 通信任务
    • 13.7.1 future 和 promise
    • 13.7.2 packaged_task
    • 13.7.3 async()
  • 13.8 建议

13.7 通信任务

标准库还在头文件 <future> 中提供了一些机制,能够让编程人员基于更高的抽象层次任务来开发,而不是直接使用低层的线程、锁:

  1. futurepromise:用于从任务(另一个线程)中返回一个值
  2. packaged_task:帮助启动任务,封装了 futurepromise,并且建立两者之间的关联
  3. async():像调用一个函数那样启动一个任务。形式最简单,但也最强大!

13.7.1 future 和 promise

futurepromise 可以在两个任务之间传值,而无需显式地使用锁,实现了高效地数据传输。其基本想法很简单:当一个任务向另一个任务传值时,把值放入 promise,通过特定的实现,使得值可以通过与之关联的 future 读出(一般谁启动了任务,谁从 future 中取结果)。

假如有一个 future<X>fx,我们可以通过 get() 获取类型 X 的值:

X v = fx.get(); // if necessary, wait for the value to get computed

如果值还没有计算出,则调用 get() 的线程阻塞,直到有值返回。如果值无法计算出,get()可能抛出异常。

promise 的主要目的是提供一个简单的“put”的操作(set_valueset_exception),和 futureget() 相呼应。

如果你有一个 promise,需要发送一个类型为 X 的结果到一个 future,你要么传递一个值,要么传递一个异常。举个例子:

void f(promise<X>& px) // 一个任务:把结果放入 px
{
    try {
        X res;
        // 计算 res 的值
        px.set_value(res);
    }
    catch(...) { // 如果无法计算 res 的值
        px.set_exception(current_exception()); // 传异常到 future 的线程
    }
}

current_exception() 即捕获到的异常。

要处理通过 future 传递的异常,get() 的调用者必须在什么地方捕获,例如:

void g(future<X>& fx) // 一个任务;从 fx 提取结果
{
    try {
        X v = fx.get(); // 如有必要,等待值计算完成
        // 使用 v
    }
    catch(...){ // 无法计算 v
        // 错误处理
    }
}

如果 g() 不需要自己处理错误,代码可以进一步简化:

void g(future<X>& fx) // 一个任务;从 fx 提取结果
{
    X v = fx.get(); // 如有必要,等待值计算完成
    // 使用 v
}

思考:futurepromise 是怎么关联起来的?

13.7.2 packaged_task

如何把 future 放入一个需要结果的任务,并且把与之关联的、产生结果的 promise 放入线程?packaged_task 可以简化任务的设置,关联 future/promisepackaged_task 封装了把返回值或异常放入 promise 的操作,并且调用 packaged_taskget_future() 方法,可以得到一个与 promise 关联的 future。举个例子,我们可以设置两个任务,借助标准库的 accumulate() 分别累加 vector<double> 的前后部分:

double accum (double* beg, double* end, double init) // 计算以 init 为初值,[beg,end) 的和
{
    return accumulate(beg,end,init);
}

double comp2(vector<double>& v)
{
    using Task_type = double(double*,double*,double); // 任务的类型

    packaged_task<Task_type> pt0 {accum}; // 打包任务(即 accum)
    packaged_task<Task_type> pt1 {accum};

    future<double> f0 {pt0.get_future()}; // 取得 pt0 的 future
    future<double> f1 {pt1.get_future()}; // 取得 pt1 的 future

    double* first = &v[0];
    thread t1{move(pt0),first,first+v.size()/2,0};          // 为 pt0 启动线程
    thread t2{move(pt1),first+v.size()/2,first+v.size(),0}; // 为 pt1 启动线程

    return f0.get() + f1.get();
}

packaged_task 模板以任务的类型(Task_typedouble(double*,double*,double) 的别名)作为其模板参数,以任务(accum)作为其构造函数的参数。move() 操作是必要的,因为 packaged_task 不可拷贝(只能移动)。packaged_task 不可拷贝是因为它是一个资源处理程序(resource handler),拥有 promise 的所有权,并且(间接地)负责与之关联的任务可能拥有的资源。

请注意,这里的代码没有显式地使用锁:我们能够专注于要完成的任务,而不是来管理它们通信的机制。这两个任务在不同的线程中执行,具有了潜在的并发性。

13.7.3 async()

我在本章所追求的思路,最简单,但也非常强大:把任务看成是一个恰巧可能和其他任务同时运行的函数。这并不是 C++ 标准库所支持的唯一模型,但它能很好地满足各类广泛的需求。其他更微妙、棘手的模型,如依赖于共享内存的编程风格也可以根据实际需要使用。

要启动潜在异步执行的任务,我们可以用 async()

double comp4(vector<double>& v) // 如果 v 足够大,派生多个任务
{
    if(v.size()<10000) // 犯得着用并发吗?
        return accum(v.begin(),v.end(),0);
    
    auto v0 = &v[0];
    auto sz = v.size();
    
    auto f0 = async(accum,v0,v0+sz/4,0.0);
    auto f1 = async(accum,v0+sz/4,v0+sz/2,0.0);
    auto f2 = async(accum,v0+sz/2,v0+sz*3/4,0.0);
    auto f3 = async(accum,v0+sz*3/4,v0+sz,0.0);
    
    return f0.get()+f1.get()+f2.get()+f3.get(); // 收集 4 部分的结果,求和
}

大体上,async() 把“调用部分”和“获取结果部分“分离开来,并且将两者和实际执行的任务分离。使用 async() 你不需要考虑线程、锁;你只要从任务(潜在地、异步地计算结果)的角度去考虑就可以了。async() 也有明显的限制:使用了共享资源、需要上锁的任务无法使用 async(),你甚至不知道会用到多少线程,这完全是由 async() 决定的,它会根据调用时系统可用资源的情况,决定使用多少线程。例如,async() 在决定使用几个线程前,会检查有多少核心(处理器)空闲。

示例代码中的猜测计算开销和启动线程的相对开销(v.size()<10000)只是一个很原始、粗略的性能估计。这里不适合展开讨论怎么去管理线程,但这个估计仅仅是一个简单(可能很烂)的猜测。

请注意,async()不仅仅是专门用于并行计算、提高性能的机制。例如,它也能用于派生任务,从用户获取输入,让“主程序”忙其他事情。

13.8 建议

  1. 使用并发改善响应性和吞吐量
  2. 尽可能在最高级别的抽象上工作(比如优先考虑 async、packaged_task 而不是 thread、mutex)
  3. 考虑使用进程作为线程的替代方案
  4. 标准库的并发支持是类型安全的
  5. 内存模型把多数程序员从考虑机器架构的工作中解放出来
  6. 内存模型使得内存的表现和我们的预期基本一致
  7. 原子操作为无锁编程提供了可能性
  8. 把无锁编程留给专家
  9. 有时顺序操作比起并发更简单、更快
  10. 避免数据竞争(不受控地同时访问可变数据)
  11. std::thread 是类型安全的系统线程接口
  12. join() 等待一个线程结束
  13. 尽量避免显式共享数据
  14. unique_lock 管理 mutexes
  15. lock() 一次性获取多个锁
  16. condition_variable 管理线程之间的通信
  17. 从(可以并行执行的)任务的角度思考,而非线程
  18. 不要低估“简单性”的价值
  19. 选择 packaged_taskfuture,而不是直接使用 threadmutex
  20. promise 返回结果,从 future 获取结果
  21. packaged_task 处理任务抛出的异常或返回值
  22. packaged_taskfuture 来表示对外部服务的请求,以及等待其回复
  23. async() 启动简单的任务

原文地址:来自 C++ 之父的多线程编程建议——现代 C++ 对多线程/并发的支持(下)

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C++ 之父的多线程编程建议——现代 C++ 对多线程/并发的支持(下) 的相关文章

  • SPI 通讯协议

    Cuitbasics 汽车ECU设计 2 2 当您将微控制器连接到传感器 xff0c 显示器或其他模块时 xff0c 您是否考虑过这两种设备是如何相互通信的 xff1f 他们到底在说什么 xff1f 事实上电子设备之间的通信就像人类之间的交
  • UART串口通讯

    UART代表通用异步接收器 发送器也称为串口通讯 xff0c 它不像SPI和I2C这样的通信协议 xff0c 而是微控制器中的物理电路或独立的IC UART的主要目的是发送和接收串行数据 xff0c 其最好的优点是它仅使用两条线在设备之间传
  • 一文搞懂AUTOSAR的DEM模块

    Dem全称为Diagnostic Event Manager xff0c 负责故障事件的处理 故障数据的存储和管理 简单说其功能是故障事件确认前的故障debounce xff0c 故障事件确认时的故障数据存储 xff0c 故障发生后的故障老
  • linux父子进程问题——孤儿进程与僵尸进程[总结]

    今天遇到一个linux进程启动时指定Max open files不对的问题 xff0c 导致程序建立socket异常 xff0c 进而导致fullgc问题 xff0c 影响正常服务 所以顺带又温习了下linux下的父子进程的特性 孤儿进程与
  • C++11/14/17一些好用新特性自己整理下

    1 override xff1a 子类继承父类的时候 xff0c 子类虚函数名字写错了或者参数列表不匹配会变成另外一个函数编译器无法判断对错 xff0c 和你写不写virtual也没关系 xff0c 这时候可以在虚函数结尾加上overrid
  • vector中emplace_back方法的用途

    在写代码的过程中 xff0c CLion提醒我把 span style background color ffd900 push back span 方法替换成 span style background color ffd900 empl
  • constexper+const+常量表达式

    常量表达式 xff08 const expression xff09 是指值不会改变并且在编译过程就能得到计算结果的表达式 显然 xff0c 字面值属于常量表达式 xff0c 用常量表达式初始化的 const 对象也是常量表达式 一个对象
  • 这篇 CPU Cache,估计也没人看

    无论你写什么样的代码都会交给 CPU 来执行 xff0c 所以 xff0c 如果你想写出性能比较高的代码 xff0c 这篇文章中提到的技术还是值得认真学习的 另外 xff0c 千万别觉得这些东西没用 xff0c 这些东西非常有用 xff0c
  • 每天一个 Linux 命令

    https blog csdn net k346k346 category 9267835 html uptime 命令 1 命令简介 uptime 用于显示系统总共运行了多长时间和系统的平均负载 无选项 uptime 命令会显示一行信息
  • Docker 安装Jenkins并配置Maven

    系统环境 系统版本 xff1a Centos7 9 docker安装参考此链接 xff1a https blog csdn net clover661 article details 122226083 下载docker时候如果报错参考 x
  • 一文详解自动驾驶的运行设计域(ODD)| 自动驾驶系列

    一文详解自动驾驶的运行设计域 xff08 ODD xff09 n 自动驾驶系列 2021年4月30日 xff0c SAE发布了第四版J3016 驾驶自动化分级 xff0c 这是即2014年1月16日 2016年9月30日 2018年6月15
  • QNX BSP分析

    QNX相关历史文章 xff1a QNX简介QNX Neutrino微内核QNX IPC机制QNX进程管理器QNX资源管理器QNX字符I OQNX之编写资源管理器 xff08 一 xff09 QNX之编写资源管理器 xff08 二 xff09
  • SOA面向服务的分布式架构详解

    导语 xff1a SOA作为一种面向服务的架构 xff0c 是一种软件架构设计的模型和方法论 从业务角度来看 xff0c 一切以最大化 服务 的价值为 出发点 xff0c SOA利用企业现有的各种软件体系 xff0c 重新整合并构建起一套新
  • 自动驾驶软件架构之:中间件与SOA(一)

    本文是将中间件作为一个专题 xff0c 专门展开进行详细的分析和讨论 中间件相关技术在计算机分布式系统中发展了很多年 xff0c 尤其在互联网服务 大型商业系统中得到广泛使用 随着智能网联汽车的发展 xff0c 现代汽车也逐步增加了以太网支
  • 嵌入式系统BSP基础知识

    嵌入式系统BSP基础知识 板级支持包 BSP 是定义如何支持特定硬件设备 设备组或硬件平台的信息集合 BSP 包括有关设备上存在的硬件功能的信息和内核配置信息以及所需的任何其他硬件驱动程序 除了用于基本和可选平台功能的通用 Linux 软件
  • constexpr

    constexpr 标志返回值或者其他表达式是常量 xff0c 在编译时就会被计算出来 这个关键字常被用来 C 43 43 const 和 constexpr 的区别 xff1f 知乎 include lt iostream gt usin
  • inline namespace

    include lt iostream gt using namespace std namespace ALL namespace V2014 void fun int num cout lt lt 34 int 34 lt lt 34
  • 进程与线程

    对于操作系统来说 xff0c 一个任务就是一个进程 xff08 Process xff09 xff0c 比如打开一个浏览器就是启动一个浏览器进程 xff0c 打开一个记事本就启动了一个记事本进程 xff0c 打开两个记事本就启动了两个记事本
  • 详解SOME/IP协议文档

    以下内容来源于AutoSar官网的AUTOSAR PRS SOMEIPProtocol文档 详解SOME IP协议文档 2 知乎 以下内容来源于AutoSar官网的AUTOSAR PRS SOMEIPProtocol文档 SOME IP P
  • AP AUTOSAR——Update and Configuration Management UCM

    15 Update and Configuration Management 15 1 What is Update and Configuration Management 更新和配置管理是Adaptive Platform Servic

随机推荐

  • 基于Docker安装Jenkins并实现CI/CD实战部署

    本实践介绍了利用Jenkins和docker技术 xff0c 如何实现CI CD的各环节的步骤 xff0c 包括环境准备 xff0c 代码提交 xff0c 编译程序 xff0c 构建镜像 xff0c 部署一套完整的安装部署流程 工具介绍 x
  • 左值引用与右值引用

    include lt iostream gt using namespace std void change int amp rnum 引用就是变量名的别名 rnum 61 111 c 43 43 中能用引用的地方 xff0c 就不要使用指
  • C++ 11的移动语义

    目录 可拷贝和可移动的概念 移动构造函数和移动赋值函数 小结移动构造和移动赋值std move 使用 std move 实现一个高效的 swap 函数Move and swap 技巧参考 可拷贝和可移动的概念 在面向对象中 xff0c 有的
  • UDS-统一诊断服务

    什么是诊断服务 xff1f 在还没有诊断服务的时候 xff0c 如果车辆故障 xff0c 需要有经验的师傅长时间的摸排查找 xff0c 费时费力 而车辆的ECU节点有了诊断模块后 xff0c 就具有了诊断功能 xff0c 这样车辆如果有了故
  • AP AUTOSAR——Network Management

    16 Network Management 16 1 What is Network Management 网络管理是Adaptive Platform Services中的一个功能集群 作为AP AUTOSAR平台的服务 xff0c 网络
  • AP AUTOSAR——Security Management

    11 Security Management 11 1 What is Security Management 安全管理是自适应平台体系结构中的一个功能集群 作为一个功能集群 xff0c 安全管理由多个模块组成 xff0c 这些模块向在Ad
  • 如何制作S32V234的Linux5.x版本BSP

    脚本是编译S32v Linux5 x版本bsp文件的流程 官方也有这个指导说明文档 xff0c 主要是第2 3章内容 xff0c 可以参考着执行 1 下面描述的所有步骤都已在Ubuntu 20 04LTS上 xff08 本机或通过虚拟机 x
  • C++经典面试题100例及答案

    1 面向对象的程序设计思想是什么 答 xff1a 把数据结构和对数据结构进行操作的方法封装形成一个个的对象 2 什么是类 答 xff1a 把一些具有共性的对象归类后形成一个集合 xff0c 也就是所谓的类 3 对象都具有的两方面特征是什么
  • C++面试100题,1——40

    C与c 43 43 有什么不同 xff1f 在c 43 43 中能使用引用就不要使用指针 xff0c 要改变一个一级指针就要用一个二级指针 要改变一个二级指针就要用一个三级指针 xff0c 会变得越来越复杂 A类中的func1是虚函数 xf
  • (TDA4 BSP )Texas Instruments Jacinto 7 J721E (DRA829/TDA4xM) BSP 如何制作?

    1 1 1 Download and Install the SDK Processor SDK Linux for J721e Documentation https software dl ti com jacinto7 esd pro
  • 解决Linux 环境 GLIBCXX_3.4.15‘ not found问题

    升级Centos系统之后 xff0c 运行filezilla时 xff0c 出现如下错误的提示信息 xff1a filezilla usr lib libstdc 43 43 so 6 version 96 GLIBCXX 3 4 15 3
  • 两台Linux服务器之间传输文件的四种方法(转载)

    在日常服务器租用中 xff0c 有时需要将文件从一台服务器传到另一台服务器 xff0c 下面给大家介绍四种linux服务器之间传输文件方式 scp 优点 简单方便 xff0c 安全可靠 xff1b 支持限速参数 缺点 不支持排除目录 用法
  • 任务间通信 | 邮箱、消息队列

    本文分享自中移OneOS公众号 任务间通信 上篇讲解了任务间同步 xff0c 在本篇中主要讲解任务间通信机制 xff0c 并对邮箱及消息队列进行详细介绍 通过对其概念 详细设计 接口设计等的讲解帮助开发者更好的理解其在操作系统中的应用 任务
  • c++ 条件变量的使用,实战

    include lt iostream gt include lt thread gt include lt mutex gt include lt condition variable gt using namespace std 线程通
  • SOA架构和微服务架构的区别

    1 SOA架构和微服务架构的区别 首先SOA和微服务架构一个层面的东西 xff0c 而对于ESB和微服务网关是一个层面的东西 xff0c 一个谈到是架构风格和方法 xff0c 一个谈的是实现工具或组件 1 SOA xff08 Service
  • 浅谈AP autosar 之 runtime 基础

    AP Autosar Architecture overview AP autosar在SOC 中的位置 xff0c 起到的作用 下面图可以看出 xff0c AP autosar封装了操作系统的接口 xff0c 封装了功能安全 xff0c
  • 「冰羚」— 撑起自动驾驶未来的“中间件”

    每当谈到自动驾驶的软件开发 xff0c 人们首先想到的 xff0c 是深不可测的人工智能算法 xff0c 是各种感知融合 xff0c 是各类路径规划 但是 xff0c 就算是再智能再高深的算法 xff0c 如果没有底层操作系统的支持 xff
  • 多核处理器的关键技术

    英特尔的cpu是从前代gt atom一路供货到第7代 xff0c 想必日常使用不会有太大区别 xff0c 而在系统之外可能存在一些散热方面的问题 而上市越早的处理器 xff0c 硬件供货越好 xff0c 可能在某些特殊时间段会出现不足 xf
  • LD_PRELOAD作用

    一 LD PRELOAD是什么 LD PRELOAD 是Linux系统的一个环境变量 xff0c 它可以影响程序的运行时的链接 xff08 Runtime linker xff09 xff0c 它允许你定义在程序运行前优先加载的动态链接库
  • C++ 之父的多线程编程建议——现代 C++ 对多线程/并发的支持(下)

    本文承接前文 现代 C 43 43 对多线程 并发的支持 xff08 上 xff09 xff0c 翻译自 C 43 43 之父 Bjarne Stroustrup 的 C 43 43 之旅 xff08 A Tour of C 43 43 x