Effective Modern C++ 第七章 并发API 2

news2024/11/16 1:17:36

目录

条款37:使std::thread型别对象在所有路径皆不可联结

要点速记:

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

要点速记:

参考:EffectiveModernCppChinese/src/7.TheConcurrencyAPI/item37.md at master · CnTransGroup/EffectiveModernCppChinese (github.com)

条款37:使std::thread型别对象在所有路径皆不可联结

每个std::thread对象处于两个状态之一:可联结的joinable)或者不可联结的unjoinable)。可结合状态的std::thread对应于正在运行或者可能要运行的异步执行线程。比如,对应于一个阻塞的(blocked)或者等待调度的线程的std::thread是可结合的,对应于运行结束的线程的std::thread也可以认为是可结合的。

不可结合的std::thread正如所期待:一个不是可结合状态的std::thread。不可结合的std::thread对象包括:

  • 默认构造的std::threads。这种std::thread没有函数执行,因此没有对应到底层执行线程上。
  • 已经被移动走的std::thread对象。移动的结果就是一个std::thread原来对应的执行线程现在对应于另一个std::thread
  • 已经被联结joinstd::thread 。在join之后,std::thread不再对应于已经运行完了的执行线程。
  • 已经被分享detachstd::thread 。detach断开了std::thread对象与执行线程之间的连接。

这使你有责任确保使用std::thread对象时,在所有的路径上超出定义所在的作用域时都是不可结合的。但是覆盖每条路径可能很复杂,可能包括自然执行通过作用域,或者通过returncontinuebreakgoto或异常跳出作用域,有太多可能的路径。

每当你想在执行跳至块之外的每条路径执行某种操作,最通用的方式就是将该操作放入局部对象的析构函数中。这些对象称为RAII对象RAII objects),从RAII类中实例化。(RAII全称为 “Resource Acquisition Is Initialization”(资源获得即初始化),尽管技术关键点在析构上而不是实例化上)。RAII类在标准库中很常见。比如STL容器(每个容器析构函数都销毁容器中的内容物并释放内存),标准智能指针(Item18-20解释了,std::uniqu_ptr的析构函数调用他指向的对象的删除器,std::shared_ptrstd::weak_ptr的析构函数递减引用计数),std::fstream对象(它们的析构函数关闭对应的文件)等。但是标准库没有std::thread的RAII类,可能是因为标准委员会拒绝将joindetach作为默认选项,不知道应该怎么样完成RAII。

幸运的是,完成自行实现的类并不难。比如,下面的类实现允许调用者指定ThreadRAII对象(一个std::thread的RAII对象)析构时,调用join或者detach

class ThreadRAII {
public:
    enum class DtorAction { join, detach };     //enum class的信息见条款10
    
    ThreadRAII(std::thread&& t, DtorAction a)   //析构函数中对t实行a动作
    : action(a), t(std::move(t)) {}

    ~ThreadRAII()
    {                                           //可结合性测试见下
        if (t.joinable()) {
            if (action == DtorAction::join) {
                t.join();
            } else {
                t.detach();
            }
        }
    }

    std::thread& get() { return t; }            //见下

private:
    DtorAction action;
    std::thread t;
};

 Item17说明因为ThreadRAII声明了一个析构函数,因此不会有编译器生成移动操作,但是没有理由ThreadRAII对象不能移动。如果要求编译器生成这些函数,函数的功能也正确,所以显式声明来告诉编译器自动生成也是合适的:

class ThreadRAII {
public:
    enum class DtorAction { join, detach };         //跟之前一样

    ThreadRAII(std::thread&& t, DtorAction a)       //跟之前一样
    : action(a), t(std::move(t)) {}

    ~ThreadRAII()
    {
        …                                           //跟之前一样
    }

    ThreadRAII(ThreadRAII&&) = default;             //支持移动
    ThreadRAII& operator=(ThreadRAII&&) = default;

    std::thread& get() { return t; }                //跟之前一样

private: // as before
    DtorAction action;
    std::thread t;
};

要点速记:

  • 在所有路径上保证thread最终是不可结合的。
  • 析构时join会导致难以调试的表现异常问题。
  • 析构时detach会导致难以调试的未定义行为。
  • 声明类数据成员时,最后声明std::thread对象。

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

Item37中说明了可结合的std::thread对应于执行的系统线程。未延迟(non-deferred)任务的future(参见Item36)与系统线程有相似的关系。因此,可以将std::thread对象和future对象都视作系统线程的句柄handles)。

从这个角度来说,有趣的是std::threadfuture在析构时有相当不同的行为。在Item37中说明,可结合的std::thread析构会终止你的程序,因为两个其他的替代选择——隐式join或者隐式detach都是更加糟糕的。但是,future的析构表现有时就像执行了隐式join,有时又像是隐式执行了detach,有时又没有执行这两个选择。它永远不会造成程序终止。这个线程句柄多种表现值得研究一下。

因为与被调用者关联的对象和与调用者关联的对象都不适合存储这个结果,所以必须存储在两者之外的位置。此位置称为共享状态shared state)。共享状态通常是基于堆的对象,但是标准并未指定其类型、接口和实现。标准库的作者可以通过任何他们喜欢的方式来实现共享状态。

我们可以想象调用者,被调用者,共享状态之间关系如下图,虚线还是表示信息流方向:

共享状态的存在非常重要,因为future的析构函数——这个条款的话题——取决于与future关联的共享状态。特别地,

  • 引用了共享状态——使用std::async启动的未延迟任务建立的那个——的最后一个future的析构函数会阻塞住,直到任务完成。本质上,这种future的析构函数对执行异步任务的线程执行了隐式的join
  • 其他所有future的析构函数简单地销毁future对象。对于异步执行的任务,就像对底层的线程执行detach。对于延迟任务来说如果这是最后一个future,意味着这个延迟任务永远不会执行了。

 

这些规则听起来好复杂。我们真正要处理的是一个简单的“正常”行为以及一个单独的例外。正常行为是future析构函数销毁future。就是这样。那意味着不join也不detach,也不运行什么,只销毁future的数据成员(当然,还做了另一件事,就是递减了共享状态中的引用计数,这个共享状态是由引用它的future和被调用者的std::promise共同控制的。这个引用计数让库知道共享状态什么时候可以被销毁。对于引用计数的一般信息参见Item19。)

正常行为的例外情况仅在某个future同时满足下列所有情况下才会出现:

  • 它关联到由于调用std::async而创建出的共享状态
  • 任务的启动策略是std::launch::async(参见Item36),原因是运行时系统选择了该策略,或者在对std::async的调用中指定了该策略。
  • 这个future是关联共享状态的最后一个future。对于std::future,情况总是如此,对于std::shared_future,如果还有其他的std::shared_future,与要被销毁的future引用相同的共享状态,则要被销毁的future遵循正常行为(即简单地销毁它的数据成员)。

只有当上面的三个条件都满足时,future的析构函数才会表现“异常”行为,就是在异步任务执行完之前阻塞住。实际上,这相当于对由于运行std::async创建出任务的线程隐式join

要点速记:

  • future的正常析构行为就是销毁future本身的数据成员。
  • 引用了共享状态——使用std::async启动的未延迟任务建立的那个——的最后一个future的析构函数会阻塞住,直到任务完成。

 

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

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

相关文章

【java爬虫】使用vue+element-plus编写一个简单的管理页面

前言 前面我们已经将某宝联盟的数据获取下来了,并且编写了一个接口将数据返回,现在我们需要使用vueelement-plus编写一个简单的管理页面进行数据展示,由于第一次使用vue编写前端项目,所以只是编写了一个非常简单的页面。 项目结…

3263页学习资料,一本在手,python不愁!

Python3.11已经发布,新的版本速度提升2倍,以弥补与其他编程语言在速度上的缺陷。可以预见Python语言在未来的应用范围会越来越广。 python学习方向建议: 如果你是本科及以下学历,建议你学习以下两个方向 爬虫。简单的爬虫库&am…

异步电机直接转矩控制学习(两电平12扇区)

导读:传统的直接转矩控制方法6扇区电压矢量选择会导致磁链控制不对称、转矩脉动大等问题,本期介绍一种把扇区细分为12扇区的新型三相异步电机直接转矩控制方法,仿真结果证明磁链轨迹、转速及转矩脉动明显变小,异步电机三相定子电流…

Mac上protobuf环境构建-java

参考文献 getting-started 官网pb java介绍 maven protobuf插件 简单入门1 简单入门2 1. protoc编译器下载安装 https://github.com/protocolbuffers/protobuf/releases?page10 放入.zshrc中配置环境变量  ~/IdeaProjects/test2/ protoc --version libprotoc 3.12.1  …

Reset信号如何同步?

首先来复习一个更加基础的概念:同步reset和异步reset。 同步reset(synchronous reset)是说,当reset信号为active的时候,寄存器在下一个时钟沿到来后被复位,时钟沿到来之前寄存器还是保持其之前的值。 异步…

【计算机组成 课程笔记】7.1 存储层次结构概况

课程链接: 计算机组成_北京大学_中国大学MOOC(慕课) 7 - 1 - 701-存储层次结构概况(15-14--)_哔哩哔哩_bilibili 这是我们已经非常熟悉的冯诺依曼计算机结构, 其中和存储功能相关的部件有:存储器和外部记录介质肯定具有…

WEB各类常用测试工具

一、单元测试/测试运行器 1、Jest 知名的 Java 单元测试工具,由 Facebook 开源,开箱即用。它在最基础层面被设计用于快速、简单地编写地道的 Java 测试,能自动模拟 require() 返回的 CommonJS 模块,并提供了包括内置的测试环境 …

Stm32_标准库_6_八种输入出模式

上拉输入与下拉输入 上拉输入:电平默认为高电平,只有当外部输入为低电平时,此IO口电平才会被拉低,经过触发器,再到寄存器,最后传入CPU GPIO_Mode_IPU;下拉输入:电平默认为低电平&am…

4.MySql安装配置(更新版)

MySql安装配置 无论计算机是否有安装其他mysql,都不要卸载。 只要确定大版本是8即可,8.0.33 8.0.34 差别不大即可。 MySql下载安装适合电脑配置属性有关,一次性安装成功当然是非常好的,因为卸载步骤是非常麻烦的 如果第一次安装…

基于SpringBoot的电影评论网站

目录 前言 一、技术栈 二、系统功能介绍 电影信息管理 电影评论回复 电影信息 用户注册 三、核心代码 1、登录模块 2、文件上传模块 3、代码封装 前言 随着信息技术在管理上越来越深入而广泛的应用,管理信息系统的实施在技术上已逐步成熟。本文介绍了电影评…

用友移动管理系统存在任意文件上传漏洞 附POC

文章目录 用友移动管理系统存在任意文件上传漏洞 附POC1. 用友移动管理系统简介2.漏洞描述3.影响版本4.fofa查询语句5.漏洞复现6.POC&EXP7.整改意见8.往期回顾 用友移动管理系统存在任意文件上传漏洞 附POC 免责声明:请勿利用文章内的相关技术从事非法测试&…

Waves 14混音特效插件合集mac/win

Waves14是一款音频处理软件,主要用于音频编辑、混音和母带处理。该软件提供了各种插件,包括EQ、压缩、混响、延迟、失真等,以及一些专业的音频处理工具,如L2限幅器、Linear Phase EQ和多频道扬声器管理。 Mac软件下载:…

朋友圈怎么定点发朋友圈?

微信朋友圈是我们日常生活中常用的社交媒体之一。但有时我们忙碌而可能会忘记发布朋友圈,或是因时间不合适而无法发布。那么,有没有一种方法可以在规定的时间内自动发布朋友圈呢? 当然有啦! 定时发朋友圈可以帮助我们在特定时间点…

re学习(38)HGAME2020-re-Level-Week1-maze

题目描述 You won’t figure out anything if you give in to fear. 学习资料: https://ctf-wiki.github.io/ctf-wiki/reverse/maze/maze-zh/ 附加说明:请走最短路线 题解 分析题目 一看题目:maze 可以确定是一个迷宫题 void __fastcall __noreturn…

使用Thrift实现跨语言RPC调用

📋 个人简介 💖 作者简介:大家好,我是阿牛,全栈领域优质创作者。😜📝 个人主页:馆主阿牛🔥🎉 支持我:点赞👍收藏⭐️留言&#x1f4d…

基于SpringBoot的智能物流管理系统

目录 前言 一、技术栈 二、系统功能介绍 顾客信息管理 员工信息管理 员工信息管理 门店信息管理 门店信息管理 订单信息管理 三、核心代码 1、登录模块 2、文件上传模块 3、代码封装 前言 随着信息技术在管理上越来越深入而广泛的应用,管理信息系统的实施…

想要精通算法和SQL的成长之路 - 无重复字符的最长子串和滑动窗口最大值

想要精通算法和SQL的成长之路 - 无重复字符的最长子串 前言一. 无重复字符的最长子串二. 滑动窗口最大值2.1 滑动窗口的基本操作 前言 想要精通算法和SQL的成长之路 - 系列导航 一. 无重复字符的最长子串 原题链接 思路如下: 用一个滑动窗口,该窗口区…

WSL 0x80071772 错误解决方案

WSL 0x80071772 错误解决方案 副标题 WSL 安装到 C 盘以外解决方案 当电脑的存储位置设置为 C 盘以为的位置时安装 WSL 会有如下报错; 原因上面也说过了,保0x80071772的错误主要是因为 WSL 安装到了 C 盘以外的位置,知道了原因也就有了如下的解决方案,该解决方案有两种 解决…

iMazing 2023年最新苹果手机怎么备份恢复照片

目前图像技术发展飞快,HDR和4K照片已经见怪不怪,这些高清照片轻而易举就可以达到10MB以上,所以大家经常会出现手机空间不足的情况,此时就需要把照片移动到电脑上备份。至于照片备份怎么弄,照片备份的软件有哪些&#x…

【Java】抽象类和接口的区别

1. 成员区别 抽象类 变量 常量;有构造方法,有抽象方法,也有非抽象方法接口 常量,抽象方法(JDK8 在接口中定义 非抽象方法) 2. 关系区别 类与类 继承单继承类与接口 实现,单实现和多实现接口…