【Example】C++ 标准库多线程同步及数据共享 (std::future 与 std::promise)

否则你会像听天书一样懵。(…)

====================================

在任何语言的多线程编程当中,必然涉及线程的同步及数据的共享,方式也有很多种。

C++ 标准库当中提供了同步及共享的方案:std::future 与 std::promise 。

头文件:

先从最基本且最原始的形式看起,std::future 与 std::promise 是互相配合使用的。

【负责访问】std::future 是一个模板类,它提供了可供访问异步执行结果的一种方式。

【负责存储】std::promise 也是一个模板类,它提供了存储异步执行的值和异常的一种方式。

先从最简单的代码入手:

以上代码和各种在你目前看来无厘头函数展示了 Print 两个线程 ID 的操作。

首先明白,std::future 负责访问,std::promise 负责存储,同时 promise 是 future 的管理者 。

进而就可以先讲简单明了的逻辑:

1,std::future 是由 std::promise 创建的 (std::async 、std::packaged_task 也可创建 future),也是作为它的管理者。

2,std::future 也仅在创建它的 std::promise、std::async 、std::packaged_task 有效时才可用。

3,std::future 可供异步操作创建者用各种方式查询、等待、提取需要共享的值,也可以阻塞当前线程等待到异步线程提供值。

4,std::future 一个实例只能与一个异步线程相关联。多个线程则需要使用 std::shared_future。

5,std::future 的共享状态是由异步操作所使用的、且与其关联的 std::std::promise 所修改。(当然你单线程修改也行,但抬杠又有什么意义)

6,std::future 禁用了拷贝构造,但是可以进行移动(move)操作。

公共成员函数表:

名称 作用

移动 future 对象,移动! share() 返回一个可在多个线程中共享的 std::shared_future 对象。 get() 获取值。(类型由模板类型而定) valid() 检查 future 是否处于被使用状态,也就是它被首次在首次调用

。 wait() 阻塞等待调用它的线程到共享值成功返回。

() 在规定时间内 阻塞等待调用它的线程到共享值成功返回。

() 在指定时间节点内 阻塞等待调用它的线程到共享值成功返回。

补充一些与 std::future 相关的枚举类型,参考自Microsoft Docs:

future_errc 枚举 : 为 future_error 类报告的所有错误提供符号名称。

名称 值 示意 broken_promise 0 与其关联的 std::promise 生命周期提前结束。 future_already_retrieved 1 重复调用 get() 函数。 promise_already_satisfied 2 与其关联的 std::promise 重复 set。 no_state 4 无共享状态。

future_status 枚举:为计时等待函数可返回的原因提供符号名称。

名称 值 示意 ready 0 就绪 timeout 1 等待超时 deferred 2 延迟执行(与std::async配合使用)

1,std::promise 负责存储,注意 std::promise 应当只使用一次。

2,std::promise 的统一初始化构造 “(p)” 是被禁用的,同时赋值运算符 “operator=” 作用为移动,std::promise 不可拷贝,但是可以被引用。

【注:此处应额外补充 alloc 构造函数】

3,std::promise 与 std::future 的状态相关联,它负责将共享值存入并给 std::future 访问使用,值类型也有可能是void、异常,当 std::future 端的阻塞函数接收到后,会立即解除阻塞状态。

4,std::promise 在作为使用者的异步线程当中,应当注意共享变量的生命周期、是否被 set 的问题。如果没有共享值没有被 set,而异步线程却结束,future 端会抛出异常。

5,std::promise 的 set 操作函数只能被调用一次。

6,std::promise 的 get_future() 函数只能被调用一次。

7,std::promise

公共成员函数表:

名称 作用 operator= 从另一个 std::promise 移动到当前对象。 swap() 交换移动两个 std::promise。 get_future() 获取与它关联的 std::future。 set_value() 设置值,类型由初始化时的模板类型而定。 set_value_at_thread_exit() 设置值,但是到该线程结束时才会发出通知。 set_exception() 设置异常,类型为 exception_ptr。 set_exception_at_thread_exit() 设置异常,但是到该线程结束时才会发出通知。

这个例子是一个线程获取 vector 当中的最大值并给另一个线程去 print。

在这个非常简单的例子当中可以看到通过 promise to future 做到了线程的同步与值的传递,还有异常的处理。

std::future 有个非常明显的问题,就是只能和一个 std::promise 成对绑定使用,也就意味着仅限于两个线程之间使用。

那么多个线程是否可以呢,可以!就是 std::shared_future。

它的语法是:

std::shared_future 也是一个模板类, 它的功能定位、函数接口和 std::future 一致,不同的是它允许给多个线程去使用,让多个线程去同步、共享:

是的, 你还看到了另一个奇怪的东西:std::packaged_task。(…)

std::packaged_task 的作用是包装一个可调用对象(可能是函数,也可能是lambda)去给异步线程调用,简化 promise to future 的流程。

它的语法:

是的,就像 std::function 那样。只不过它是用来给异步线程调用的:

成员函数表:

名称 作用

移动 std::packaged_task 对象,移动!

检查可调用对象是否有效。

交换移动两个 std::packaged_task。

返回具有相关联异步状态的 std::future 对象。

执行该可调用对象。 make_ready_at_thread_exit 执行该可调用对像,但是到该线程结束时才会发出通知。 reset() 重置,并清空之前的值。

将上文例子变种演示一下:

使用它需要注意的事项:

1,std::packaged_task 不能被拷贝,但是可以被移动,也可以被引用。

2,std::packaged_task 可以默认无参构造,但此时没有任何作用,执行会发生异常,valid() 值为 false。

3,std::packaged_task 的 get_future() 函数只能被调用一次。

4,std::packaged_task 绑定了可调用对象并已经运行,它的共享状态会一直持续到与它关联的 std::future 或最后一个 std::shared_future 结束为止。

5,std::packaged_task 应谨慎操作,它本身的生命周期应持续到所有与它关联的 future 结束后为止。

std::async 是一个函数模板,作用是异步运行可调用对象,最终将调用结果返回到 std::future 当中。

它的语法是:

launch 枚举:展示描述模板函数 async 的可能模式的位掩码类型

名称 值 示意 async 0 异步调用 主动 deferred 1 延迟调用 被动

这两个枚举代表什么效果呢? 请仔细看非常简单的例子:

三次运行效果:

是的,最直观的就是:

std::launch::async 是在 std::async 初始化所有线程局域对象后执行可调用对象。

std::launch::deferred 是在 std::async 初始化后(期间完成内部std::thread对象创建),不执行可调用对象(内部std::thread也没有被初始化),在 std::async 返回的 std::future 首次调用非定时等待函数后,再去执行。

这就是[异步调用主动]与[延迟调用被动]的区别。

注意的是,如果不传第一个枚举参数,那么,std::async 优先使用哪种 launch 取决于编译器的实现机制。

额外技术细节请参考 C++ Reference:

函数模板 async 异步地运行函数 f (潜在地在可能是线程池一部分的分离线程中),并返回最终将保有该函数调用结果的 std::future 。
1) 表现如同以 policy 为 std::launch ::async | std::launch ::deferred 调用 (2) 。换言之, f 可能执行于另一线程,或者它可能在查询产生的 std::future 的值时同步运行。
2) 按照特定的执行策略 policy ,以参数 args 调用函数 f

  • 若设置 async 标志(即(policy & std::launch ::async ) != 0 ),则 async 在新的执行线程(初始化所有线程局域对象后)执行可调用对象 f ,如同产出 std::thread (std::forward
  • 若设置 deferred 标志(即(policy & std::launch ::deferred ) != 0 ),则 async 以同 std::thread 构造函数的方式转换 fargs... ,但不产出新的执行线程。而是进行 惰性求值:在 async 所返回的 std::future 上首次调用非定时等待函数,将导致在当前线程(不必是最初调用 std::async 的线程)中,以 args... (作为右值传递)的副本调用 f (亦作为右值)的副本。将结果或异常置于关联到该 future 的共享状态,然后才令它就绪。对同一 std::future 的所有后续访问都会立即返回结果。
  • policy 中设置了std::launch::async 和 std::launch::deferred 两个标志,则进行异步执行还是惰性求值取决于实现。
  • 【C++ 14 开始】若 policy 中未设置 std::launch::async 或 std::launch::deferred 或任何实现定义策略标志,则行为未定义。

任何情况下,对 std::async 的调用 同步于(定义于 std::memory_order )对 f 的调用,且 f 的完成 _先序于_令共享状态就绪。若选择 async 策略,则关联线程的完成 _同步于_首个等待于共享状态上的函数的成功返回,或最后一个释放共享状态的函数的返回,两者的先到来者。

完工!

2022-03-19 凌晨 4:23

AirChip org

Original: https://www.cnblogs.com/airchip/p/16024336.html
Author: 芯片烤电池
Title: 【Example】C++ 标准库多线程同步及数据共享 (std::future 与 std::promise)

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/606300/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • linux 修改文件的创建时间—–touch命令

    1、首先不会用touch 可以自己man touch查看并学习,推出man按q 2、举例 给文件修改时间 touch -mt 201909052248 test.log -m mo…

    Linux 2023年6月13日
    091
  • USB转RS232串口应用

    RS232串口是用于数据串行通信传输的标准之一,该标准定义了信号的电气特性和时序、信号的含义以及连接器的物理尺寸和引脚排列。RS232协议规范定义的是DB25接口,实际上大多数RS…

    Linux 2023年6月7日
    0101
  • WPF 应用启动过程同时启动多个 UI 线程且访问 ContentPresenter 可能让多个 UI 线程互等

    在应用启动过程里,除了主 UI 线程之外,如果还多启动了新的 UI 线程,且此新的 UI 线程碰到 ContentPresenter 类型,那么将可能存在让新的 UI 线程和主 U…

    Linux 2023年6月6日
    083
  • Mysql数据库语言学习的路线

    对于我们数据库的学习,不管是测试人员还是开发人员以及我们的DBA来说重点都是SQL;但是我们的SQL可以分多少类型,学习重点又是在哪里呢,本文仅仅针对测试人员来展开说明: SQL:…

    Linux 2023年6月14日
    079
  • 分布式中灰度方案实践

    让请求在导航的服务节上点执行; 一、背景简介 分布式系统中会存在这样的开发场景,不同需求可能涉及到对同一个服务的开发,那么该服务在研发期间就会存在多个版本并行的状态,为了保持不同版…

    Linux 2023年6月14日
    0107
  • SpringBoot入门 ->(个人学习记录笔记)

    1. 入门 1.1 导入依赖 所有springboot工程都必须继承spring-boot-starter-parent org.springframework.boot spri…

    Linux 2023年6月8日
    062
  • shell之磁盘容量检查,配合crontab可以定时清理磁盘

    我的做法: !/bin/bashAvailable=df -k | sed -n 2p | awk ‘{print $4}’if [ $Available -eq 0 ];then…

    Linux 2023年5月28日
    078
  • 人人都写过的5个Bug!

    大家好,我是良许。 计算机专业的小伙伴,在学校期间一定学过 C 语言。它是众多高级语言的鼻祖,深入学习这门语言会对计算机原理、操作系统、内存管理等等底层相关的知识会有更深入的了解,…

    Linux 2023年6月14日
    093
  • CentOS7 小技巧总结

    1.CentOS7 解决无法使用tab自动补全 csharp;gutter:true; 原因:CentOS在最小化安装时,没有安装自动补全的包,需要手动安装。 yum -y ins…

    Linux 2023年6月7日
    0113
  • gitlab备份迁移与升级

    bash;gutter:false; 升级计划: https://docs.gitlab.com/ee/update/index.html#upgrade-paths</p&…

    Linux 2023年6月7日
    067
  • 高通MSM8998 ABL的调试

    高通在MSM8998上引入了UEFI,用来代替LK(Little Kernel)。高通UEFI由XBL和ABL两部分组成。XBL负责芯片驱动及充电等核心应用功能。ABL包括芯片无关…

    Linux 2023年6月7日
    074
  • 四大高阶函数、匿名函数、递归

    四大高阶函数: map、reduce、filter、sorted 1.map函数: 根据提供的函数对指定序列做映射 使用可迭代对象(指定的序列)中的每个元素调用函数,将返回值作为新…

    Linux 2023年6月8日
    0101
  • SQL87 最差是第几名(一)

    本题链接表结构如下所示。 +——-+——–+| grade | number |+——-+&#8…

    Linux 2023年6月13日
    076
  • 数据库简单查询

    简单查询 语法句式如下: SELECT filed1,filed2 … filedn FROM tablename [WHERE CONDITION11] [GROUP BY …

    Linux 2023年6月7日
    0117
  • 【Leetcode】53. 最大子数组和

    给你一个整数数组 nums,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。 子数组是数组中的一个连续部分。 示例 1: &#x8F93;&am…

    Linux 2023年6月6日
    084
  • 个人学习-STL:Set前置-tree

    参考资料: [1]程杰.大话数据结构[M]. [2][美]Robert Sedgewic,Jevin Wayne. 算法Algorithms[M].谢路云译 1.基本脉络: 树实际…

    Linux 2023年6月6日
    091
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球