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

news2024/9/29 15:20:37

条款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>参见条款39

class 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.coloradmin.cn/o/808208.html

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

相关文章

springboot中配置bpmsjs插件-activiti7流程图绘制插件/IDEA中运行bpmnjs

BPMNJS的安装和使用需要依赖nodejs插件,需要先安装NODEJS,因为bpmnjs插件的运行需要使用到NODEJS中的npm命令。 安装nodejs 安装和使用bpmnjs插件,绘制activiti工作流需要的流程图。 1、安装和配置nodejs 2.1、下载nodejs https://nodejs.org/en 1.2、安装nodejs,默认安…

基于 Docker 的深度学习环境:Windows 篇

本篇文章&#xff0c;我们聊聊如何在 Windows 环境下使用 Docker 作为深度学习环境&#xff0c;以及快速运行 SDXL 1.0 正式版&#xff0c;可能是目前网上比较简单的 Docker、WSL2 配置教程啦。 写在前面 早些时候&#xff0c;写过一篇《基于 Docker 的深度学习环境&#xff…

C++设计模式::享元模式(combination)-可运行

实现: 1) cShape:抽象接口; cShape*:具体实现的接口; 2) cFactory:按照传入参数color来区别对象, 如果已经创建过, 那就返回已有的, 否则创建新的. 使用: 传入参数, 获取被创建的对象(创建尽可能少的对象) 1) 设计框架 /*shape.hpp*/ #pragma once #if…

CSS伪元素详解以及伪元素与伪类的区别

伪元素常常被误解为伪类&#xff0c;主要在于他们的语法相似&#xff0c;都是对于选择器功能的扩展&#xff0c;相似程度很高导致被混淆。 本文通过详细介绍伪元素和常见的使用方法&#xff0c;最后也会分析下伪元素与伪类的基本区别。 基本描述 CSS伪元素也是应用于选择器的…

渗透测试:Linux提权精讲(一)之sudo方法第一期

目录 写在开头 CVE-2019-14287 sudo apt和sudo apt-get sudo apache2 sudo ash sudo awk sudo base32/58/64/nc/z sudo cp sudo cpulimit sudo curl sudo date sudo dd sudo dstat sudo ed sudo env sudo exiftools 总结与思考 写在开头 在进行渗透测试获取…

sql server表值函数

一、创建测试表 Employees 二、创建表值函数 -- DROP FUNCTION TableIntSplit;CREATE FUNCTION TableIntSplit(Text NVARCHAR(4000),Sign NVARCHAR(4000)) RETURNS tempTable TABLE(Id INT ) AS BEGIN DECLARE StartIndex INT DECLARE FindIndex INT DECLARE Content VARCHAR(…

浅谈 Spring AOP 思想

Spring AOP AOP 切面编程普通代理类JDK动态代理Cglib动态代理AOPAOP术语AOP切面编程的优势Advice通知类型&#xff08;5种&#xff09;通知的执行顺序 Order切入点表达式表达式execution注解annotation Spring事务管理Transactional 及 Transactional 的两个属性Transactional …

AR开发平台 | 探索AR技术在建筑设计中的创新应用与挑战

随着AR技术的不断发展和普及&#xff0c;越来越多的建筑师开始探索AR技术在建筑设计中的应用。AR(增强现实)技术可以通过将虚拟信息叠加到现实场景中&#xff0c;为设计师提供更加直观、真实的建筑可视化效果&#xff0c;同时也可以为用户带来更加沉浸式的体验。 AR开发平台广…

SpringBoot项目连接数据库

1、找到applications.yml&#xff0c;如下图 2、写入代码 server:port: 9494spring:datasource:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/自己的数据库表名?serverTimezoneGMT%2b8username: rootpassword: root

短视频矩阵源码开发搭建分享--多账号授权管理

目录 文章目录 前言 一、矩阵号系统是什么&#xff1f; 二、使用步骤 1.创建推广项目 2.多账号授权 3.企业号智能客服系统 总结 前言 短视频多账号矩阵系统&#xff0c;通过多账号一键授权管理的方式&#xff0c;为运营人员打造功能强大及全面的“矩阵式“管理平台。…

修改整数(有点坑,所以发出来了)

问题描述 小贝给了小聪一个正整数 x&#xff0c;但是小聪决定把这个数改掉。她可以把整数 x 每个位置上的数 t 改成 9-t。 请你帮助小聪来计算一下&#xff0c;如何把 x 改成一个最小的正整数&#xff0c;注意&#xff0c;不能出现首位为 0 的情况。 输入格式 输入一个正整数…

Vue通过指令 命令将打包好的dist静态文件上传到腾讯云存储桶 (保存原有存储目录结构)

1、在项目根目录创建uploadToCOS.js文件 (建议起简单的名字 方便以后上传输入命令方便) 2、uploadToCOS.js文件代码编写 const path = require(path); const fs = require(fs); const COS = require(cos-nodejs-sdk-v5);// 配置腾讯云COS参数 const cos = new COS({SecretI…

NoSQL-Redis集群

NoSQL-Redis集群 一、集群&#xff1a;1.单点Redis带来的问题&#xff1a;2.解决&#xff1a;3.集群的介绍&#xff1a;4.集群的优势&#xff1a;5.集群的实现方式&#xff1a; 二、集群的模式&#xff1a;1.类型&#xff1a;2.主从复制&#xff1a; 三、搭建主从复制&#xff…

vscode 打开文件时如何在资源管理器中展开文件所在的整个目录树(包含node_modules)

如题。去 首选项 --> 设置 中 搜索 “Auto Reveal”&#xff0c;然后选true&#xff0c;注意把下面的Auto Reveal Exclude排除项中的node_modules去掉&#xff0c;这样才能定位到node_modules中的文件。 **/node_modules

【FPGA IP系列】FIFO的通俗理解

FPGA厂商提供了丰富的IP核&#xff0c;基础性IP核都是可以直接免费调用的&#xff0c;比如FIFO、RAM等等。 本文主要介绍FIFO的一些基础知识&#xff0c;帮助大家能够理解FIFO的基础概念。 一、FIFO介绍 FIFO全称是First In First Out&#xff0c;即先进先出。 FIFO是一个数…

函数重载与引用

文章目录 一、函数重载1. 重载规则2.重载列子3.函数名修饰规则 二、引用1.本质2.特性1. 引用必须在定义时初始化2 . 一个变量可以有多个引用3 . 引用一旦引用一个实体&#xff0c;就不能引用其他实体 3.引用例子4.引用的权限5.效率比较6.指针跟引用的区别 一、函数重载 函数重…

我的创作纪念日 --- 鲁迅文学无聊版

机缘 ------从第一次使用CSDN这个网站到现在已经快四年了&#xff0c;我大抵是病了&#xff0c;2021年7月29日才心血来潮写下来了第一篇自己的博客&#xff0c;回顾起来&#xff0c;已经过去了2年。如此这般&#xff0c;断断续续的写过一些博客&#xff0c;但终归是心血来潮罢了…

【Docker】Docker的优势、与虚拟机技术的区别、三个重要概念和架构及工作原理详细讲解

前言 Docker 是一个开源的应用容器引擎&#xff0c;让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的Linux或Windows操作系统的机器上,也可以实现虚拟化,容器是完全使用沙箱机制,相互之间不会有任何接口。 作者简介&#xff1a; 辭七七&#xf…

PS - Photoshop 实现涂抹功能 (橡皮擦、图章、吸管、画笔)

欢迎关注我的CSDN&#xff1a;https://spike.blog.csdn.net/ 本文地址&#xff1a;https://spike.blog.csdn.net/article/details/131997323 在 Photoshop 中&#xff0c;橡皮擦工具&#xff0c;以及吸管工具和画笔工具可以配合使用&#xff0c;实现涂抹功能&#xff0c;再通过…

第四届世界蜂疗大会在中国·重庆武隆盛大开幕

【39蜂疗网】记者 讯 7月25日至27日&#xff0c;“世界中联蜂疗专业委员会第五届学术年会暨第四届世界蜂疗大会、中国民族医药学会蜂疗分会2023年学术年会”在重庆武隆正式启幕。开幕式上&#xff0c;重庆市政府副市长但彦铮宣布开幕&#xff1b;人民英雄、中国工程院院士张伯礼…