C++11——线程库的理解与使用

目录

前言

一、线程库的构造

1.默认构造 

2.带参构造 

3.拷贝构造与赋值拷贝(不支持)

4.移动构造

二、线程调用lambda函数

三、线程安全与锁

1.lambda中的线程与锁

2.函数指针中的线程与锁

3.trylock()

4.recursive_mutex

5.lock_gurad守卫锁

6.unique_lock

四、条件变量

五、atomic

六、shared_ptr的多线程问题

七、懒汉模式中的线程安全问题


前言

之前我们学习过Linux的线程库thread,他是使用 POSIX 标准,而Windows中的线程库,使用的其他标准,在C++11之前他们的接口是不一样的,对代码的编写就很麻烦,Linux一套,Windows一套。C++11出来之后,引入了标准的线程库,使得在不同平台上编写具有可移植性的多线程代码变得更加容易。

一、线程库的构造

之前我们创建线程,都要先pthread_t 创建一个线程tid,然后把线程id,线程属性,一个函数指针,还有参数都传递过去。如果参数较多,我们还得封装成一个结构体,把结构体地址传过去。这并不是很方便,也不是很体面。

1.默认构造 

C++11提供了线程的默认构造(创建线程,但不启动)

 使用方法很简单,创建即可,一般要配合移动构造使用。(后面会讲)

2.带参构造 

带参构造,第一个参数为可调用对象(函数指针,仿函数,lambda函数,包装器)并且函数返回值并不限制,第二个参数为可变参数包,也就是我们不需要再有一个线程id,和设置线程属性,同时也不需 要再封装成一个结构体了,直接将参数传递给参数包,他自动解析就可以了。

现在我们来使用一下C++11的线程构造,如下,让 t 线程去执行Print函数。其中this_thread作用域下的get_id()能获取当前进程的id。

运行发现让id为19524的线程去执行了函数。 

多个参数也很简单,直接传递参数即可,不再需要使用结构体。 

3.拷贝构造与赋值拷贝(不支持)

thread库并支持拷贝构造与赋值拷贝,如果允许拷贝构造或拷贝赋值,那么会导致多个线程对象管理同一个底层线程,这可能会引发竞争条件和资源管理问题。

4.移动构造

线程的移动构造是在线程即将死亡(被回收)的时候,把资源给另外一个线程。因为这样线程依然只属于一个人,不会造成多个线程对象管理同一个底层线程的情况。

同时他能配合默认构造一起使用,因为默认构造,你根本都没传递函数,你怎么能让线程去处理任务呢?配合移动构造,就能让默认构造的线程也跑起来。

move是将t2转为将亡值。t1去夺舍t2。

同时,这样我们也可以使用容器管理线程了。因为反正有默认构造,vector只传入 int 整形就是在进行默认构造。后面再通过移动赋值让线程运行。

 小总结:

  1. 带参构造,创建可执行线程
  2. 先创建空线程对象,移动构造或者移动赋值将右值对象转移过去

二、线程调用lambda函数

我们知道,线程需要调用一个可调用对象,让他去执行这个可调用对象,从而让线程运行起来。其中运用最多的就是普通函数和lambda函数,这是因为让线程执行任务,我们只需要把任务说明白就行,包装器更适合提取函数的类型,仿函数又不够轻量化。

线程调用lambda函数比较简单,代码如下,在lambda捕获列表进行捕获就可以了。

三、线程安全与锁

1.lambda中的线程与锁

我们定义一个变量,让两个线程同时去对这个变量做++操作,按道理结果应该是20000,但是有可能结果不如我们的预期,这就是多线程导致的数据安全问题,++x并不是原子操作。

我们需要对临界资源进行上锁,来保证临界资源的安全。C++11的mutex库提供的mutex默认构造,直接使用即可

代码如下,对++x进行加锁与解锁 

2.函数指针中的线程与锁

在函数指针中,我们也传递一把锁,让他去保护临界资源x,这里都传递的是引用,按照之前的学习,我们的代码是没有问题的,这里却发生报错,编译不通过。

这是因为你传递的参数是传递给thread带参构造的可变参数包的,并不是我们看到的直接传参,他还会有一些处理。

thread 构造函数会对传递的参数进行拷贝或移动,然后将这些拷贝或移动后的参数传递给线程函数,导致在新线程中修改的实际是拷贝或移动后的引用,而不是原始的引用,因此会发生错误。

添加ref代表强制引用, 因此我们这里记住添加ref就好

这确实比较麻烦,我们也可以选择用指针,就可以避免这些问题。

3.trylock()

锁的lock()如果现在申请不到锁,就会在锁的等待队列上等待,直到轮到了自己,才会申请锁成功。而trylock()申请锁失败不会进入等待队列进行等待,而是返回false,继续往后执行。

4.recursive_mutex

revursive_mutex是递归互斥锁,线程在持有锁的情况下,再递归调用自己的函数,如果是普通的互斥锁就会发生死锁,此时需要使用递归互斥锁,防止死锁。

5.lock_gurad守卫锁

再我们对临界资源进行加锁解锁时,可能会发生一些意外,比如把解锁写错了,写成加锁

当然,这个错误比较低级,但如果临界区代码发生了异常呢?

大家看如下代码,我们在加锁与解锁中模拟了一个异常情况。

如果发生了异常,catch 块中的代码会被执行,然后程序会继续执行 try-catch 块之后的代码,而不会回到抛出异常的地方继续执行,于是我们的锁就不会解锁,后续线程想申请锁,就申请不到了,这会导致死锁的发生。

因此我们需要利用RAII的思想,使用lock_guard来守护线程,也就是把锁资源交给一个类,让类构造时申请资源,析构时自动释放资源。

使用如下代码进行mutex资源守护,注意成员变量和构造参数一定要引用,因为锁是不支持拷贝的,使用引用代表指的一直是这一把锁。 

template<class Lock>
class LockGuard
{
public:LockGuard(Lock& lk):_lk(lk){_lk.lock();}~LockGuard(){_lk.unlock();}
private:Lock& _lk;
};

那么线程使用上了LockGuard,出了作用域会自动析构,也就是说你catch捕获异常的时候,我就会析构了,然后释放锁资源,就不会死锁了。 

 std库也给我们设计好了 lock_guard,拿来用就可以了。

6.unique_lock

unique_lock也可以完成守卫锁的任务,他比守卫锁多了手动的加锁与解锁,同时可以与time_mutex进行配合。

四、条件变量

condition_variable就是条件变量,他只有默认构造。同时wait函数需要传递的锁是unique_lock类型。传入unique_lock就是要让wait先去解锁之后再去等待,lock_guard不支持手动解锁

条件变量本质就是通知,告诉等待你可以去再申请锁了。notify_one是通知某一个线程,notify_all通知所有线程。如果没有线程等待,就不做处理。

如下代码,就运用了条件变量,实现让线程1线程2轮流打印。

首先是使用unique_lock进行加锁,一开始flag为false,因此t1线程不会去等待,肯定是t1线程先打印,再将flag置为true,再通知t2线程。

此时t2线程要么在锁的地方阻塞住,要么就比t1线程更先运行,已经判断过flag为 false了,在wait中进行等待,被通知了就继续运行了,因此就可以实现交替打印了。

#include<iostream>
#include<thread>
#include<vector>
#include<mutex>
using namespace std;
int main()
{int n = 20;bool flag = false;mutex mtx;condition_variable cv;thread t1([n, &flag,&mtx,&cv] {for (int i = 1; i <= n; i++){if(i%2==1){unique_lock<mutex> lock(mtx);//加锁,出作用域自动解锁if (flag){cv.wait(lock);//wait先解锁在去等待队列等待}cout << i << endl;flag = true;cv.notify_one();//信号变量通知其他线程取消等待}}});thread t2([n, &flag, &mtx, &cv] {for (int i = 1; i <= n; i++){if (i % 2 == 0){unique_lock<mutex> lock(mtx);//加锁,出作用域自动解锁if (!flag){cv.wait(lock);//wait先解锁在去等待队列等待}cout << i << endl;flag = false;cv.notify_one();//信号变量通知其他线程取消等待}}});t1.join();t2.join();
}

五、atomic

前面我们的代码临界区都不算很长,此时使用互斥锁的效率就会变得很低,因为互斥锁保证原子操作,会导致线程切换时间片浪费的情况。

比如线程 thread_1 加锁了,正在++x,还没有解锁,此时时间片到了。被切换了,线程 thread_2 来了,想去申请锁,却一直申请不到,就会在等待队列等待,白白浪费了自己的时间片。C++11提供了atomic来保证变量的原子性。

当然,只建议对内置类型的处理,如果传入的类型是自定义类型,代码比较长的话,那还是用互斥锁吧。

他主要设计到了CAS(compare and swap)操作,如下代码代替了++x;

其中 atomic_compare_exchage_wead 用于比较并交换操作。它用于在原子方式下比较内存中的值&x和给定的期望值&old,如果它们相等,则将新值newval写入内存,并返回 true;否则不写入,并返回 false。

也就是会再去检查内存中x的值,发现是x==old的,证明此时其他线程并没有参与进来,那么你写入新值返回true就完事,如果发现内存中x的值与old不相等,也就不会写入并返回false。

六、shared_ptr的多线程问题

我们知道,多个 shared_ptr 可以共同拥有同一个对象,这里面有一个引用计数。当我们去拷贝shared_ptr的时候,都会对该引用计数进行++操作。

如果是多线程的情况下,去拷贝会不会发生问题呢?

如下代码,本应该对 (*sp)++了20000次,结果却不是20000。

 当我们对资源加锁后,发现*sp的值就是我们预想的了。

由此可得出结论:shared_ptr本身是线程安全的,但是他保护的资源不是线程安全的

七、懒汉模式中的线程安全问题

懒汉模式是在需要时才会创建对象实例,而不是在程序启动时就创建,因此我们之前只有一个执行流执行的时候,只需要判断他的成员变量指针 _instance 是否为空就可以了,为空就创建再返回,不为空就直接返回该指针。

但如果是多线程的情况,可能有很多线程一起起来访问,可能会执行很多new Singleton()。导致数据不一致问题,因此我们得进行加锁。

但如果仅仅是加锁,那么每次线程调用GetInstance()的时候都要去申请锁,效率会很低下。

因此我们可以再在最外层判断一下_instance是否为nullptr,双重保险,让效率提升,如果不为nullptr,那么就直接返回,不用再申请锁了。

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

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

相关文章

JAVA-服务器搭建-创建web后端项目

首先打开IDEA 点击新建项目 写好名称-模板选择 Web应用程序 -语言选择 Java 构建系统选择 Maven 然后点击下一步 选择版本-选择依赖项 Web Profile 点击创建 点击当前文件-选择编辑配置 选择左上角的加号-选择Tomcat服务器-选择本地 点击配置-选择到Tomcat目录-点击确定 起个…

利用STM32 HAL库实现USART串口通信,并通过printf重定向输出“Hello World“

一、开发环境 硬件&#xff1a;正点原子探索者 V3 STM32F407 开发板 单片机&#xff1a;STM32F407ZGT6 Keil版本&#xff1a;5.32 STM32CubeMX版本&#xff1a;6.9.2 STM32Cube MCU Packges版本&#xff1a;STM32F4 V1.27.1 上一篇使用STM32F407的HAL库只需1行代码实现US…

云仓酒庄广西发布会盛启:新老经销商欢聚南宁

原标题&#xff1a;云仓酒庄广西发布会盛启&#xff1a;新老经销商欢聚南宁&#xff0c;共襄精酿啤酒盛宴在夏日的热情与激情中&#xff0c;云仓广西发布会于今日在美丽的南宁盛大开幕。来自各地的经销商们齐聚一堂&#xff0c;共同见证了这一盛况。此次发布会不仅是一次产品的…

2024年汉字小达人活动还有5个月开赛:来做18道历年选择题备考吧

现在距离2024年第11届汉字小达人比赛还有五个多月的时间&#xff0c;如何利用这段时间有条不紊地备考呢&#xff1f;我的建议是两手准备&#xff1a;①把小学1-5年级的语文课本上的知识点熟悉&#xff0c;重点是字、词、成语、古诗。阅读理解不需要。②把历年真题刷刷熟&#x…

数电期末复习(一)数制和码制

数制和码制 1.1 概述1.2 几种常用的数制1.2.1 十进制&#xff08;Decimal&#xff09;1.2.2 二进制&#xff08;Binary&#xff09;1.2.3 二-十进制之间的转换1.2.4 十六进制和八进制1.2.5 任意进制之间的转换 1.3 二进制代码1.3.1 二-十进制码(BCD Binary Coded Decimal)1.3.2…

2023年网络安全行业:机遇与挑战并存

2023年全球网络安全人才概况 根据ISC2的《2023年全球网络安全人才调查报告》&#xff0c;全球的网络安全专业人才数量达到了550万&#xff0c;同比增长了8.7%。然而&#xff0c;这一年也见证了网络安全人才短缺达到了历史新高&#xff0c;缺口数量接近400万。尤其是亚太地区&am…

ARM_day6:实现字符串数据收发函数的封装

程序代码&#xff1a; uart4.h&#xff1a; #ifndef __UART4_H__ #define __UART4_H__ #include"stm32mp1xx_gpio.h" #include"stm32mp1xx_rcc.h" #include"stm32mp1xx_uart.h" void uart4_config(); void putchar(char dat); char getchar();…

【电机参数】直流无刷电机机械转速、ud、uq、us、输出功率、相反电动势幅值、载波周期、转矩常数

【电机参数】直流无刷电机机械转速、ud、uq、us、输出功率、相反电动势幅值、载波周期、转矩常数 前言 【电机控制】直流有刷电机、无刷电机汇总——持续更新 使用工具&#xff1a; 1.示波器&#xff1a;PICO2205A 2.电桥LCR&#xff1a;VICIOR4090A 3.电流钳&#xff1a;汉泰…

接口测试相关

接口测试&#xff0c;接口 接口是数据交互的入口和出口 接口是一套规范和标准 统一设计标准 前后端相对独立 扩展型灵活 接口文档。 接口测试 接口测试环境&#xff0c;运行程序&#xff0c;自己搭建环境 接口测试插件 谷歌postman 火狐 restclient java测试工具为j…

Linux系统的磁盘管理与文件系统

目录 一、磁盘结构 1.物理结构 2.数据结构 二、MBR与磁盘分区表示 1.MBR 2.磁盘分区表示 分区的优点 分区的缺点 三、文件系统类型 1.文件系统的组成 XFS SWAP EXT4 2.磁盘管理工具 四、Linux系统添加新硬盘的步骤 一、磁盘结构 1.物理结构 所有存储的设备都在…

【面试经典 150 | 数组】最后一个单词的长度

文章目录 写在前面Tag题目来源解题思路方法一&#xff1a;遍历 写在最后 写在前面 本专栏专注于分析与讲解【面试经典150】算法&#xff0c;两到三天更新一篇文章&#xff0c;欢迎催更…… 专栏内容以分析题目为主&#xff0c;并附带一些对于本题涉及到的数据结构等内容进行回顾…

Android 性能优化之黑科技开道(二)

3. 其它可以黑科技优化的方向 3.1 核心线程绑定大核 3.1.1 定义 核心线程绑定大核的思路也很容易理解&#xff0c;现在的 CPU 都是多核的&#xff0c;大核的频率比小核要高不少&#xff0c;如果我们的核心线程固定运行在大核上&#xff0c;那么应用性能自然会有所提升。 核…

【Qt】Qt界面构建与对象管理:从 “Hello World“ 到内存释放

文章目录 1. 通过图形化界面创建控件2. 通过纯代码方式创建控件3. 对象树管理与内存管理小结&#xff1a; 在软件开发中&#xff0c;构建用户界面是至关重要的一步。Qt作为一个跨平台的C框架&#xff0c;提供了强大的界面构建工具和对象树管理机制&#xff0c;使得界面开发变得…

单页面首屏优化,打包后大小减少64M,加载速度快了13.6秒

需求背景 从第三方采购的vue2 ElementUI实现的云管平台&#xff0c;乙方说2011年左右就开始有这个项目了&#xff08;那时候有Vue了吗&#xff0c;思考.jpg&#xff09;。十几年的项目&#xff0c;我何德何能可以担此责任。里面的代码经过多人多年迭代可以用惨不忍睹来形容&a…

Qt做关于界面

在上位机系统中&#xff0c;经常需要显示当前软件版本&#xff0c;当前fpga版本&#xff0c;那么咱就做一个help的菜单的关于界面 解决的问题&#xff1a; 解决显示版本信息的UI Axure界面图&#xff1a; 如何实现&#xff1a; 实现文件的存储&#xff1a;QSetting来存储版…

玩转Virtual Box虚拟机

玩转Virtual Box虚拟机 虚拟化技术和虚拟机简介 什么是虚拟化技术&#xff1f; 虚拟化技术是将计算机的各种硬件资源予以抽象、转换、分割、组合的一种计算机技术。虚拟化技术打破了实体结构间不可切割的障碍&#xff0c;从而使用户可以按照需求重新组合硬件资源&#xff0c…

NX二次开发UF_MTX(矩阵运算)常用函数

目录 一、概述 二、函数的介绍 2.1 UF_MTX3_copy&#xff08;复制原来矩阵&#xff09; 2.2 UF_MTX3_determinant&#xff08;计算矩阵的行列式&#xff09; 2.3 UF_MTX3_identity&#xff08;单位矩阵&#xff09; 2.4 UF_MTX3_initialize&#xff08;可以根据X、Y方向向…

R: 阿尔法α多样性计算和箱图制作,以及差异分析

# install.packages("vegan") library(vegan) library(ggplot2) library(ggpubr)setwd("xxx") # 使用read.table()函数读取数据 df <- read.table("xxx", header TRUE, row.names 1)# 转置数据框 df <- t(df)# 计算每个样品的香农多样性…

【Linux】虚拟机与Xshell及VS Code的连接

一、基础环境 虚拟机&#xff1a;VMware Workstation Pro 虚拟机镜像&#xff1a;ubuntu-18.04.5-desktop-amd64.iso 其他&#xff1a;Xshell 6、Xftp 6、Visual Studio Code 上述软件的安装操作不再赘述&#xff0c;CSDN上有大量的优秀博文&#xff0c;可参考&#xff1a;详细…

SpringBoot---------Lombook

Lombok是一个可以通过简单的注解形式来帮助我们简化消除一些必须有但显得很臃肿的Java代码的工具&#xff0c;通过使用对应的注解&#xff0c;可以在编译源码的时候生成对应的方法&#xff0c;也就是简化咱们之前pojo&#xff0c;实体类里面臃肿的get/set有参无参。 首先查看一…