条款38:对变化多端的线程句柄析构函数行为保持关注

条款37解释过,可联结的线程对应着一个底层系统执行线程,未推迟任务(参见条款36)的期值和系统线程有类似关系。这么一来,std::thread型别对象和期值对象都可以视作系统线程的句柄。

从这个视角来看,std::thread对象和期值对象的析构函数表现出如此不同的行为值得深思。正如条款37所提及的,针对可联结的std::thread型别对象实施析构会导致程序终止,因为另外两个显而易见的选择(隐式join和隐式detach)都被认为是更糟糕的选择。而期待的析构函数呢,有时候行为像是执行了一次隐式join,有时候行为像是执行了一次隐式detash,有时候行为像是二者都没有执行。它从不会导致程序终止。这套线程句柄行为的大杂烩,值得我们仔细品鉴一番。

我们的观察从这里开始:期值位于信道的一端,被调方把结果通过该信道传输给调用方。被调方(通常以异步方式运行)把其计算所得的结果写入信道(通常经由一个std::promise型别对象),而调用方则使用一个期值来读取该结果。你可以把这个过程想成下图,虚线箭头代表着从被调方向调用方的信息流:

 但被调方的结果要存储在哪里呢?在调用方唤起对应期值的get之前,被调方可能已经执行完毕,因此结果不会存储在被调用方的std::promise型别对象里。那个对象,对于被调方来说就是个局部量,在被调方结束后会实施析构。

该结果也不能存储在调用方的期值中,因为(出于其他种种原因)可能会从std::future型别对象出发创建std::shared_future型别对象(因此把被调方结果的所有权从std::future型别对象转移至std::shared_future型别对象),而后者可能会在原始的std::future析构之后复制多次。如果被调方的结果型别不都是可复制的(即只移型别),而该结果至少生存期要延至和最后一个指涉到它的期值一样长。这么多个对应同一结果的期值中的哪一个,应该包含该结果呢?

既然与被调方相关联的对象和与调用方相关联的对象都不适合作为被调方结果的存储之所,那就只能将该结果存储在位于两者外部的某个位置。这个位置称为共享状态。共享状态通常使用堆上的对象来表示,但是其型别、接口和实现标准皆未指定。标准库作者可以自由地用他们喜好的方法去实现共享状态。

我们可以把调用方、被调用方和共享状态之间的关系使用下图来表示,虚线箭头仍然表示着信息流:

 共享状态的存在很重要,因为期值析构函数的行为(这也是本条款的议题)是由与其关联的共享状态决定的。具体来说就是:

  • 指涉到经由std::async启动的未推迟任务的共享状态的最后一个期值会保持阻塞,直至该任务结束。本质上,这样一个期值的析构函数是对底层异步执行任务的线程实施了一次隐式join.
  • 其他所有期值对象的析构函数只仅仅将期值对象析构就结束了。对于底层异步运行的任务,这样做类似于对线程实施了一次隐式detach.对于那些被推迟任务而言,如果这一期值是最后一个,也就意味着被推迟的任务将不会有机会运行了。

这些规则听上去复杂,其实不然,我们真正需要关心的,是一个平凡的"常规"行为外加一个不甚常见的例外而已。常规行为是指期值的析构函数仅会析构期值对象。就这样。它不会针对任何东西实施join,也不会从任何东西实施detach,也不会运行任何东西。它仅会析构期值的成员变量(好吧,实际上,它还多做了一件事。它针对共享状态里的引用计数实施了一次自减。该共享状态由指涉到它的期值和被调方的std::promise共同操作。该引用计数使得库能知道何时可以析构共享状态。关于引用计数的一般材料,参见条款19)。

而相对于正常行为的那个例外,只有在期值满足以下全部条件时才会发挥作用:

  • 期值所指涉的共享状态是由于调用了std::async才创建的
  • 该任务的启动策略是std::launch::async。这既可能是运行时系统的选择,也可能是在调用std::async时指定的。
  • 该期值是指涉到该共享状态的最后一个期值,对于std::future型别对象而言,这一点总是成立。而对于std::shared_future型别对象而言,在析构时如果不是最后一个指涉到共享状态的期值,则它会遵循常规行为准则(即仅析构其成员变量)

只有当所有条件都满足,期值的析构函数才会表现出特别行为。而行为的具体表现为阻塞直到异步运行的任务结束。从效果来看,这相当于针对正在运行的std::async所创建的任务的线程实施了一次隐式join。

经常会有人把这个例外和常规期望析构函数行为的差异说成是“来自std::async的期值会在其析构函数里被阻塞。”如果只是最粗略的近似,这种说法也不为错,但有时候你需要比最粗略的近似了解得更深一步。而现在,你已经知道了全面的真相。

抑或你的疑问又并不同,可能会是“为什么要为从std::async出发启动的非推迟任务相关联的共享状态专门制定一条规则?”问得合理。根据我所知道的,标准委员会想要避免隐式detach相关的问题(参见条款37),但是他们又不想简单粗暴地让程序终止了事(他们针对可联结线程就是这样做的,参见条款37),所以妥协结果就是实施一次隐式join.这个决定并非没有争议,委员会也曾认真讨论过要在C++14中舍弃这样的行为,但是最后没有做出改变,所以期值析构函数的行为在C++11和C++14中是保持了一致的。

期值的API没有提供任何方法判断其指涉的共享状态是否诞生于std::async的调用,所以给定任意期值对象的前提下,它不可能知道自己是否会在析构函数中阻塞到异步任务执行结束。这个事实暗示着一些意味深长的推论:

//该容器的析构函数可能会在其析构函数中阻塞,
//因为它所持有的期值中可能会有一个或多个
//指涉到经由std::async启动未推迟任务所产生的共享状态
std::vector<std::future<void>> futs;   //关于std::future<void>参见条款39class Widget{
public:                              //Widget型别对象可能会在其析构函数中阻塞...private:std::shared_futrue<double> fut;
};

当然,如果有办法判定给定的期值不满足触发特殊析构行为的条件(例如,通过分析程序逻辑),即可断定该期值不会阻塞在其析构函数中。例如,只有因std::async调用而出现的共享状态才够格去展示特别行为,但是还有其它方法可以创建出共享状态。其中一个方法就是运用std::packaged_task,std::packaged_task型别对象会准备一个函数(或其他可调用的对象)以供异步执行,手法是将它加上一层包装,把其结果置入共享状态。而指涉到该共享状态的期值则可以经由std::packaged_task的get_future函数得到:

int calcValue();  //待运行的函数std::packaged_task<int()> pt(calcValue);  //给calcValue加上包装使之能以异步方式运行auto fut = pt.get_future();  //取得pt的期值

此时此刻,我们已知期值对象fut没有指涉到由std::async调用产生的共享状态,所以它的析构函数将表现出常规行为。

std::packaged_task型别对象pt一经创建,就会运行在线程之上(它也可以经由std::async的调用而运行,但是如果你要用std::async运行任务,就没有很好的理由再去创建什么std::packaged_task型别对象,因为std::async能够在调度任务执行之前就做到std::packaged_task能够做到的任何事情)。

std::packaged_task不能复制,所以欲将pt传递给std::thread的构造函数就一定要将它强制转型有右值(经由std::move,参加条款23):

std::thread t(std::move(pt));  //在t之上运行pt

此例让我们能够隐约看出一些期值的常规析构行为,但如果把这些语句都放在同一代码块中,就可以看得更加清楚:

{                        //代码块开始std::packaged_task<int()> pt(calcValue);auto fut = pt.get_future();std::thread t(std::move(pt));...                  //见下}                        //代码块结束

这里最值得探讨的代码是“...”部分,它位于t创建之后、代码块结束之前。值得探讨的是,在‘...’中t的命运如何。基本存在三种可能:

  • 未对t实施任何操作。在这种情况下,t在作用域结束点是可联结的,而这将导致程序终止
  • 针对t实施了join.在此情况下,fut无须在析构函数中阻塞,因为在调用的代码已经有过join
  • 针对t实施了detach。在此情况下,fut无须在任何析构函数中实施detach,因为在调用的代码已经做过了这件事了

换句话说,当你的期值所对应的共享状态是由std::packaged_task产生的,则通常无需采用特别的析构策略。因为,关于是终止、联结还是分离的决定,会由操纵std::thread的代码作出,而std::packaged_task通常就运行在该线程之上。

要点速记

  • 期值的析构函数在常规情况下,仅会析构期值的成员变量
  • 指涉到经由std::async启动的未推迟任务的共享状态的最后一个期值会保持阻塞,直至该任务结束

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/12894.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Opencv的Mat内容学习

来源&#xff1a;Opencv的Mat内容小记 - 知乎 (zhihu.com) 1.Mat是一种图像容器&#xff0c;是二维向量。 灰度图的Mat一般存放<uchar>类型 RGB彩色图像一般存放<Vec3b>类型。 (1)单通道灰度图数据存放样式&#xff1a; (2)RGB三通道彩色图存放形式不同&#x…

Flutter 添加 example流程

一、已有Flutter工程&#xff08;命令&#xff09;添加 example 1、cd 工程(flutter_plugin ,是自己创建的)根目录 例: flutter create example 执行命令创建example PS&#xff1a;cd example 后执行flutter doctor 后就可以看到效果 2、如果需要指定iOS/Android 语言,请添加…

如何建立Docker私有仓库?

文章目录 docker私有仓库harborHarbor仓库部署Harbor仓库使用 docker私有仓库 Docker 私有仓库是一个用于存储和管理 Docker 镜像的私有存储库。它允许你在内部网络中创建和管理 Docker 镜像&#xff0c;并提供了更好的安全性和控制&#xff0c;因为你可以完全控制谁能够访问和…

ansible自动化运维(一)

&#x1f618;作者简介&#xff1a;正在努力的99年公司职员。 &#x1f44a;宣言&#xff1a;人生就是B&#xff08;birth&#xff09;和D&#xff08;death&#xff09;之间的C&#xff08;choise&#xff09;&#xff0c;做好每一个选择。 &#x1f64f;创作不易&#xff0c;…

机器学习 day31(baseline、学习曲线)

语音识别的Jtrain、Jcv和人工误差 对于逻辑回归问题&#xff0c;Jtrain和Jcv可以用分类错误的比例&#xff0c;这一方式来代替单单只看Jtrain&#xff0c;不好区分是否高偏差。可以再计算人类识别误差&#xff0c;即人工误差&#xff0c;作为基准线来进行比较Jtrain与baselin…

回归预测 | MATLAB实现TCN-BiLSTM时间卷积双向长短期记忆神经网络多输入单输出回归预测

回归预测 | MATLAB实现TCN-BiLSTM时间卷积双向长短期记忆神经网络多输入单输出回归预测 目录 回归预测 | MATLAB实现TCN-BiLSTM时间卷积双向长短期记忆神经网络多输入单输出回归预测预测效果基本介绍模型描述程序设计参考资料 预测效果 基本介绍 1.MATLAB实现TCN-BiLSTM时间卷积…

Thymeleaf入门

Thymeleaf是前端开发模板&#xff0c;springboot默认支持。前端模板用法大多数是类似的jsp、thymeleaf、vue.js都有while\for\if\switch等使用&#xff0c;页面组件化等。 1.前端模板区别 jsp是前后端完全不分离的&#xff0c;jsp页面写一堆Java逻辑。 thymeleaf好处是html改…

非Spring环境 | Mybatis-Plus插入数据返回主键两种方式(注解或XML)

废话不多说&#xff0c;直接撸代码: <?xml version"1.0" encoding"UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace&qu…

【Spring】什么是Bean的生命周期及作用域,什么是Spring的执行流程?

博主简介&#xff1a;想进大厂的打工人博主主页&#xff1a;xyk:所属专栏: JavaEE进阶 在前面的播客中讲解了如何从Spring中存取Bean对象&#xff0c;那么本篇我们来讲解Bean对象的生命周期是什么&#xff0c;Bean对象的6种作用域分别是什么&#xff0c;都有哪些区别&#xff…

通过STM32内部ADC将烟雾传感器发送的信号值显示在OLED上

一.CubeMX配置 首先我们在CubeMX配置ADC1, 设置一个定时器TIM2定时1s采样一次以及刷新一次OLED&#xff0c; 打开IIC用于驱动OLED显示屏。 二.程序 在Keil5中添加好oled的显示库&#xff0c;以及用来显示的函数、初始化函数、清屏函数等。在主程序中初始化oled,并将其清屏。…

【RTT驱动框架分析02】-串口驱动分析

串口驱动学习 0.串口驱动的使用方法 //定义一个时间 struct rt_event system_event; #define SYS_EVENT_UART_RX_FINISH 0x00000001 /* UART receive data finish event *//*串口接收回调函数 Receive data callback function */ static rt_err_t uart_input(rt_device_t …

掌握Python的X篇_16_list的切片、len和in操作

接上篇掌握Python的X篇_15_list容器的基本使用&#xff0c;本篇进行进一步的介绍。 文章目录 1. list的索引下标可以是负数2. 切片&#xff08;slice&#xff09;2.1 切片基础知识2.2 如何“取到尽头”2.3 按照步长取元素2.4 逆序取值 3. len函数获取lis的元素个数4. in操作符…

rocketmq客户端本地日志文件过大调整配置(导致pod缓存cache过高)

现象 在使用rocketmq时&#xff0c;发现本地项目中文件越来越大&#xff0c;查找发现在/home/root/logs/rocketmqlog目录下存在大量rocketmq_client.log日志文件。 配置调整 开启slf4j日志模式&#xff0c;在项目启动项中增加-Drocketmq.client.logUseSlf4jtrue因为配置使用的…

手把手教你从0入门线段树~

1. 什么是线段树? 1.1 初探线段树 定义&#xff1a;线段树是一种用于解决区间查询问题的数据结构&#xff0c;是一种广义上的二叉搜索树。 原理&#xff1a;它将一个区间划分为多个较小的子区间&#xff0c;并为每个子区间存储一些有用的信息&#xff0c;例如最大值、最小值…

如何降低TCP在局域网环境下的数据传输延迟

以Ping为例。本案例是一个测试题目&#xff0c;只有现象展示&#xff0c;不含解决方案。 ROS_Kinetic_26 使用rosserial_windows实现windows与ROS master发送与接收消息_windows 接收ros1 消息 什么是ping&#xff1f; AI&#xff1a; ping是互联网控制消息协议&#xff08;…

【Spring Boot】

目录 &#x1f36a;1 Spring Boot 的创建 &#x1f382;2 简单 Spring Boot 程序 &#x1f370;3 Spring Boot 配置文件 &#x1f36e;3.1 properties 基本语法 &#x1fad6;3.2 yml 配置文件说明 &#x1f36d;3.2.1 yml 基本语法 &#x1f369;3.3 配置文件里的配置类…

如何将ubuntu LTS升级为Pro

LTS支持周期是5年&#xff1b; Pro支持周期是10年。 Ubuntu Pro专业版笔记 步骤&#xff1a; 打开“软件和更新” 可以看到最右侧的标签是Ubuntu Pro。 在没有升级之前&#xff0c;如果使用下面两步&#xff1a; sudo apt updatesudo apt upgrade 出现如下提示&#xff…

【低代码专题方案】iPaaS运维方案,助力企业集成平台智能化高效运维

01 场景背景 随着IT行业的发展和各家企业IT建设的需要&#xff0c;信息系统移动化、社交化、大数据、系统互联、数据打通等需求不断增多&#xff0c;企业集成平台占据各个企业领域&#xff0c;成为各业务系统数据传输的中枢。 集成平台承接的业务系统越多&#xff0c;集成平台…

【数据结构】时间复杂度和空间复杂度

&#x1f4d9; 作者简介 &#xff1a;RO-BERRY &#x1f4d7; 学习方向&#xff1a;致力于C、C、数据结构、TCP/IP、数据库等等一系列知识 &#x1f4d2; 日后方向 : 偏向于CPP开发以及大数据方向&#xff0c;欢迎各位关注&#xff0c;谢谢各位的支持 时间复杂度和空间复杂度 前…

diffusion model(五)stable diffusion底层原理(latent diffusion model, LDM)

LDM: 在隐空间用diffusion model合成高质量的图片&#xff01; [论文地址] High-Resolution Image Synthesis with Latent Diffusion Models [github] https://github.com/compvis/latent-diffusion 文章目录 LDM: 在隐空间用diffusion model合成高质量的图片&#xff01;系列…