当前位置: 首页 > news >正文

《C++并发编程实战》笔记(一、二)

一、简介

抽象损失:对于实现某个功能时,可以使用高级工具,也可以直接使用底层工具。这两种方式运行的开销差异称为抽象损失。

二、线程管控

2.1 线程的基本控制

1. 创建线程

线程相关的管理函数和类在头文件:

#include <thread>

创建一个线程使用如下方法:

std::thread t(callable);
  • callable:线程函数,可以是任意的可调用对象
  • 线程对象创建后会立即启动线程运行

2. 控制线程的结束

线程启动后,必须显式指定线程结束的方式:

阻塞等待其结束(汇合),使用如下方法:

t.join();
  • 只要调用了join(),主线程会阻塞等待该线程执行结束,join()执行结束后,隶属于该线程的任何存储空间都会被清除,且线程对象不再关联到结束的线程
  • 成员函数t.joinable()返回线程对象t是否关联到某个线程,当join()执行结束后t.joinable()会返回false
  • 只有关联到某个线程的对象才能调用join()

让线程后台运行(分离),使用如下方法:

t.detach();
  • 线程对象必须关联某个线程(joinable()true时)才能调用detach,调用后线程的归属权和控制权都转移给C++运行时库,它独立于主线程继续运行,直至线程函数运行结束,且C++运行时库会保证线程退出后与之关联的资源都被正确回收
  • detach的线程不再关联实际的执行线程(joinable()会返回false),所以不可再调用join()

注意:如果线程对象销毁时都没有指定结束方式,则std::thread的析构函数会调用std::terminate()终止整个程序

3. 异常时保证线程正常结束

创建线程对象后,如果在指定线程结束方式前,因为执行其他代码造成了异常,可能会导致指定线程结束方式的代码被略过,从而导致程序终止。即:

void func() {try {// 线程 t 去执行 do_something() 函数std::thread t(do_something);// 此时如果发生异常等,下面的join可能无法被调用,退出func时程序会被中断do_other_thing();} catch (...) {throw;}t.join();return;
}

解决方法:

  1. 一种解决方法是保证在程序的所有执行路径都会指定线程结束方式
    void func() {try {std::thread t(do_something);do_other_thing();} catch (...) {// 保证一定会指定线程结束方式t.join();throw;}t.join();return;
    }
    
  2. 使用RAII方法,利用对象管理线程,在构造函数中创建线程,在析构函数中指定线程的结束方式
    class RaiiThreadGuard {public:explicit RaiiThreadGuard(std::thread &t) : t_(t) {} ~RaiiThreadGuard() {// 类对象离开作用域时一定会调用析构,保证一定会指定线程对象的结束方式t_.join();}// 将拷贝构造和拷贝赋值都定义为删除的,避免编译器优化造成重复调用析构RaiiThreadGuard(const RaiiThreadGuard &) = delete;RaiiThreadGuard& operator=(const RaiiThreadGuard &) = delete;private:std::thread& t_;
    };// main 函数中通过如下方式使用
    int main() {// 创建线程std::thread t(HelloFunction);// 使用 RAII 保证线程对象一定会调用 joinRaiiThreadGuard thread_guard(t);// 即使后续再执行其他代码造成退出作用域,编译器会保证执行 thread_guard 的析构指定线程的结束方式return 0;
    }

2.2 向线程函数传递参数

线程函数所需要的参数可以直接紧跟在std::thread的线程函数实参后:

  • 线程具有内部存储空间,线程函数的实参会先使用拷贝构造函数,将std::thread的实参复制到创建的线程;在创建好的线程中,新复制出来的实参被当成临时量,以右值形式传递给新线程中的线程函数
  • 根据参数的传递过程,如果线程函数包含非const引用形参,为避免在线程内执行时收到右值,需要通过std::refstd::thread传递实参(与std::bind函数的使用相同)
  • 类的非静态成员函数第一个参数是指向对象的隐式this指针,如果想在线程中执行某个成员函数,需要将对象地址传递给成员函数的隐式this指针
class TestClass {
public:/** @brief  默认构造函数 */TestClass(){std::cout << "TestClass default constructor." << std::endl;std::cout << "std::thread::id: " << std::this_thread::get_id() << std::endl;}/** @brief  拷贝构造函数 */TestClass(const TestClass& ohter) {std::cout << "TestClass copy constructor." << std::endl;std::cout << "std::thread::id: " << std::this_thread::get_id() << std::endl;}/** @brief  移动构造函数 */TestClass(TestClass&& ohter) {std::cout << "TestClass move constructor." << std::endl;std::cout << "std::thread::id: " << std::this_thread::get_id() << std::endl;}/** @brief  类的内部函数 */void InnerFunction() {std::cout << "TestClass Inner Function." << std::endl;std::cout << "std::thread::id: " << std::this_thread::get_id() << std::endl;}
};void func(int num, std::string &str, TestClass obj) {str += std::to_string(num);
}// main 函数
std::string str("The num: ");
TestClass test_class;
// 参数直接作为 std::thread 的后续参数传入
// 1. 对象会先调用拷贝构造复制到线程,再通过std::move()以右值复制给线程内的函数形参
// 2. 线程函数的引用形参要通过 std::ref() 传递
std::thread t(func, 3, std::ref(str), test_class);
t.join();
std::cout << str << std::endl;// 在线程中运行类的成员函数,需要将对象的地址传递给成员函数的隐式this指针
std::thread t2(&TestClass::InnerFunction, &test_class);
t2.join();/* 输出
TestClass default constructor.
std::thread::id: 140737348195264
TestClass copy constructor.
std::thread::id: 140737348195264
TestClass move constructor.
std::thread::id: 140737348179520
The num: 3
TestClass Inner Function.
std::thread::id: 140737348179520
*/

2.3 转移线程归属权

在某些情况下,如想要指定特定的函数等待线程结束,可能需要转移线程的归属权。

std::threadstd::unique类似,由于独占资源,其对象不能被复制,只支持移动

std::thread t1(some_function);
std::thread t2 = std::move(t1);

2.4 运行时确定线程数量

标准库的静态函数std::thread::hardware_concurrency()函数返回程序在执行时可以真正并发的线程数量:

  • 若信息无法获取,返回0
  • 否则返回支持并发的线程数

2.5 标识不同线程

每个线程都有一个唯一的ID,该ID是一个std::thread::id类型的变量:

  • 可以使用线程对象的std::thread::get_id()函数返回线程对象的ID
  • 在程序中可以使用std::this_thread::get_id()函数获取运行当前程序的线程ID
  • 默认构造创建的std::thread::id类型变量表示线程不存在

std::thread::joinable()函数会利用线程对象的ID确定返回值,即:

  • this.get_id() != std::thread::id() 则返回 true(判断当前线程ID和默认构造的线程ID类型变量是否相同)
  • 否则返回false(表示线程对象没有关联到任何线程,线程不存在)

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 抗量子密码算法:保障未来信息安全的新盾牌
  • 比赛获奖的武林秘籍:06 5 分钟速通比赛路演答辩,国奖选手的血泪经验!
  • 《JavaScript权威指南第7版》中文PDF+英文PDF+源代码 +JavaScript权威指南(第6版)(附源码)PDF下载阅读分享推荐
  • Hadoop-25 Sqoop迁移 增量数据导入 CDC 变化数据捕获 差量同步数据 触发器 快照 日志
  • 手机和电脑通过TCP传输
  • Boost搜索引擎
  • 构建Memcached帝国:分布式部署策略与实践指南
  • uni-app 保存号码到通讯录
  • Kithara与OpenCV (二)
  • 观察者模式的实现
  • 海外短剧开源系统UNIAPP源码(支持多语言/海外支付/快捷登录)
  • 【Docker 系列】学习路线
  • Xcode多任务处理指南:释放iOS应用的并发潜能
  • 快速上手文心一言:让创作更轻松
  • TensorFlow系列:第五讲:移动端部署模型
  • $translatePartialLoader加载失败及解决方式
  • Android 初级面试者拾遗(前台界面篇)之 Activity 和 Fragment
  • iOS编译提示和导航提示
  • Selenium实战教程系列(二)---元素定位
  • vagrant 添加本地 box 安装 laravel homestead
  • Vue ES6 Jade Scss Webpack Gulp
  • 当SetTimeout遇到了字符串
  • 精益 React 学习指南 (Lean React)- 1.5 React 与 DOM
  • 力扣(LeetCode)357
  • 名企6年Java程序员的工作总结,写给在迷茫中的你!
  • 深度学习中的信息论知识详解
  • 物联网链路协议
  • 一个JAVA程序员成长之路分享
  • 《TCP IP 详解卷1:协议》阅读笔记 - 第六章
  • Spark2.4.0源码分析之WorldCount 默认shuffling并行度为200(九) ...
  • ​一些不规范的GTID使用场景
  • #include<初见C语言之指针(5)>
  • #pragam once 和 #ifndef 预编译头
  • #Ubuntu(修改root信息)
  • (¥1011)-(一千零一拾一元整)输出
  • (6)【Python/机器学习/深度学习】Machine-Learning模型与算法应用—使用Adaboost建模及工作环境下的数据分析整理
  • (八)c52学习之旅-中断实验
  • (补充):java各种进制、原码、反码、补码和文本、图像、音频在计算机中的存储方式
  • (初研) Sentence-embedding fine-tune notebook
  • (力扣)循环队列的实现与详解(C语言)
  • (原創) 如何刪除Windows Live Writer留在本機的文章? (Web) (Windows Live Writer)
  • (源码版)2024美国大学生数学建模E题财产保险的可持续模型详解思路+具体代码季节性时序预测SARIMA天气预测建模
  • (转)Google的Objective-C编码规范
  • ***汇编语言 实验16 编写包含多个功能子程序的中断例程
  • .gitignore
  • .gitignore文件_Git:.gitignore
  • .NET Core 实现 Redis 批量查询指定格式的Key
  • .NET Core实战项目之CMS 第十二章 开发篇-Dapper封装CURD及仓储代码生成器实现
  • .NET Standard / dotnet-core / net472 —— .NET 究竟应该如何大小写?
  • .NET WPF 抖动动画
  • .NET 中创建支持集合初始化器的类型
  • .net 逐行读取大文本文件_如何使用 Java 灵活读取 Excel 内容 ?
  • .NET/ASP.NETMVC 深入剖析 Model元数据、HtmlHelper、自定义模板、模板的装饰者模式(二)...
  • .NET/C# 判断某个类是否是泛型类型或泛型接口的子类型
  • .Net6 Api Swagger配置