【C++】C++11的新特性 — 线程库 ,原子操作 , 条件变量

news2024/9/24 21:24:38

在这里插入图片描述

勇敢就是接受发生在你身上的事,并把它尽力做到最好。
-- 约翰・欧文 --

C++11的新特性

  • 1 线程
    • 1.1 线程概念
    • 1.2 C++中的线程
    • 1.3 线程并行
    • 1.4 锁
  • 2 原子操作
  • 3 条件变量
  • Thanks♪(・ω・)ノ谢谢阅读!!!
  • 下一篇文章见!!!

1 线程

1.1 线程概念

在Linux中我们了解了什么是线程:
【Linux】从零开始认识多线程 — 线程概念与底层实现
【Linux】从零开始认识多线程 — 线程控制
【Linux】从零开始认识多线程 — 线程ID
【Linux】从零开始认识多线程 — 线程互斥

线程:在进程内部运行,是CPU调度的基本单位,共享一个地址空间。Linux下线程本质是一种轻量化进程,可以在一个进程中并发运行不同的任务。同时Linux为了避免出现同时访问的问题,保证线程互斥,可以加入互斥锁!

在语言层,每个语言都封装了线程库,内部封装了底层的系统调用,让上层更加方便的使用。

1.2 C++中的线程

c++中线程被设计成了一个类来方便我们的使用:
在这里插入图片描述

我们可以快捷通过创建一个对象来快速创建线程,也可以调用对象的join接口来进行等待!

我们来看构造函数:
在这里插入图片描述
默认构造是创建一个无参的空线程。一般创建时要传入需要执行的函数方法,和一个参数包!在linux下,如果我们想要传入多个参数,就要想办法将这些参数进行一个整合,即在堆上开辟一个结构体来让线程获取。而在C++11中,不需要进行结构体的传递,通过可变参数包的方法就可以满足!

来看一个例子:

void Print(int n)
{
	for (int i = 0; i < n; i++)
	{
		cout << i << endl;
	}
	cout << endl;
}

int main()
{
	thread t(Print , 100);
	
	t.join();
	return 0;
}

我们构造的时候加入print100,就可以单独设置一个线程来运行Print函数。
在这里插入图片描述
获取线程id接口 get_id

  1. 主线程中获取新线程的id直接进行调用即可
  2. Print接口中获取自己的id就比较复杂,因为Print函数中并没有线程对象?那怎么办可以通过this_thread类(全局的一些数据)。可以通过this_threadget_id来获取主线程id,来侧面验证

在这里插入图片描述

1.3 线程并行

再来运行两个线程我们来看看:
在这里插入图片描述
并行执行会出现一些换行的问题!
当对同一个全局变量进行操作时,如果操作不是原子的,就很有可能导致一些错误,这些错误是偶发性的,不容易复刻。我们来通过一个大数据情况下的运行看看:
在这里插入图片描述
为了避免这样运行的错误,我们可以进行加锁:

  1. mutex类是对底层的锁的系统调用进行的封装
  2. 通过lockunlock可以快捷的进行上锁解锁!
int x = 0;
//全局锁
mutex tex;

void Print(int n)
{
	//这个for循环不是临界区,处在线程独立的栈中
	for (int i = 0; i < n; i++)
	{
		tex.lock();
		//对全局数据的操作是临界的
		x++;
		tex.unlock();
	}
}
int main()
{
	thread t1(Print, 10000);
	thread t2(Print, 20000);

	t1.join();
	t2.join();

	cout << x << endl;
	return 0;
}

在进入临界区之前加锁,保证非原子的操作中不会受到其他线程的打扰!同样的也会发生频繁的上下文切换,导致运行效率变低!

如果不使用全局变量呢?我们可以通过参数传递过去:

void Print(int n , int& rx , mutex& rtex)
{
	for (int i = 0; i < n; i++)
	{
		tex.lock();
		x++;
		tex.unlock();
	}
}

int main()
{
	mutex tex;
	int x = 0;
	//这样无法直接传入过去
	thread t1(Print, 10000 , x , tex);
	thread t2(Print, 20000 , x , tex);

	t1.join();
	t2.join();

	cout << x << endl;
	return 0;
}

当我们向这样运行的时候,就会发现,报错了!
在这里插入图片描述
因为这里 x , tex不是直接传入到Print函数中的,而是进行thread构造,里面会有这些参数,因为是一个左值引用也就是进行一个深拷贝。thread的底层是pthread_create,需要特定类型的函数指针和参数:
在这里插入图片描述
所以为了可以保证一直是引用,可以使用ref()保证传值拷贝的时候是引用

在这里插入图片描述

	thread t1(Print, 10000 , ref(x) , ref(tex));
	thread t2(Print, 20000 , ref(x) , ref(tex));

这样就可以正常运行了!这里略显麻烦,为了适配底层的系统调用(C语言版本)需要付出一些代价!

我们再来看一个混合使用:lambda表达式,十分的优雅!
我们使用lambda表达式进行所有变量的引用捕捉,就能获取到x tex

	int x = 0;
	mutex tex;
	//捕捉所有的
	thread t1([&]()
		{
			for (int i = 0; i < 10000; i++)
			{
				tex.lock();
				x++;
				tex.unlock();
			}
		});
	thread t2([&]()
		{
			for (int i = 0; i < 20000; i++)
			{
				tex.lock();
				x++;
				tex.unlock();
			}
		});

	t1.join();
	t2.join();

优雅!

再来看:如果我们想要创建很多线程,但是先不使用。等待后续才进行使用。我们可以使用vector容器来进行储存线程,需要时就进行遍历来获取空的线程对象,对空的线程对象进行移动赋值!

int main()
{
	vector<thread> vthd;
	int n = 0;
	cin >> n;
	//进行初始化
	vthd.resize(n);

	int x = 0;
	mutex tex;

	auto func = [&](int n)
		{
			for (int i = 0; i < n; i++)
			{
				tex.lock();
				x++;
				tex.unlock();
			}
		};

	for (auto& thd : vthd)
	{
		//移动赋值
		thd = thread(func, 10000);
	}

	for (auto& thd : vthd)
	{
		thd.join();
	}

	cout << x << endl;

	return 0;
}

优雅!

1.4 锁

C++11中提供了很多种锁:
在这里插入图片描述
其中mutex中的接口有:

  1. lock:上锁 — 阻塞的,没锁可以使用就进行阻塞
  2. unlock:解锁
  3. try_lock:上锁 — 非阻塞的 ,没有锁可用就返回false

其中timed_mutex,在mutex的基础上加入了时间限定函数:

  1. try_lock_for :可以设置上锁的时间
  2. try_lock_until : 上锁到对应时间点

其中recursive_mutex递归锁,可以在递归函数中进行使用,防止死锁的问题!

实际用法到具体使用时在细细研究就好!

我们再来看一个比较巧妙的方法:

class LockGuard
{
public:
	LockGuard(mutex& mtx):
		_mtx(mtx)
	{
		_mtx.lock();
	}
	~LockGuard()
	{
		_mtx.unlock();
	}
private:
	mutex& _mtx;
};

通过这个类,我们可以在临界区前创建一个锁守卫,生命周期结束就会自动解锁!为了不会锁住非临界区的数据,可以使用{ }划定局部域!库中提供了模版锁守卫lock_guard,可以方便使用!

2 原子操作

我们需要进行一些非原子操作的时候,比如++,或者修改一个全局的flag。使用锁操作有些大炮打蚊子的感觉,这时可以使用原子操作来进行!

在这里插入图片描述
其底层是cas先比较再设置,保证操作的原子性!
我们来试着使用一下:

atomic<int> x = 0;

auto func = [&](int n)
	{
		for (int i = 0; i < n; i++)
		{
			x++;
		}
	};

这样就了可以保证操作的原子性了,比加锁简单多了!
获取其中的数据可以使用load接口,修改数据可以使用exchange接口…

3 条件变量

条件变量经常使用在多线程环境下,它允许线程在某些条件不满足时挂起(等待),直到另一个线程更新了共享数据并通知条件变量,使得等待的线程可以继续执行。条件变量主要提供以下接口:

  1. wait():阻塞当前线程,直到条件变量被唤醒,通常在互斥锁锁定的情况下调用,进入wait之前会进行一个解锁!
  2. wait_for():阻塞当前线程,直到条件变量被唤醒或给定的时间超时。
  3. wait_until():阻塞当前线程,直到条件变量被唤醒或到达某个特定的时间点。
  4. notify_one:通知一个线程开始工作,如果等待的超过1个,就会进行随机选择!
  5. notify_all:唤醒所有线程

我们来看一个例子:

我们来实现:两个线程交替打印奇偶数,我们来通过这个了解条件变量:
创建10个线程,都有对应1 - 10 的ID号,每次只能打印一个线程的id,如果ready为真就wait ,为假才继续进行。进行打印之前将ready设置为真,打印结束设置为假!
这里我们加入一个计时的接口this_thread::sleep_for(std::chrono::seconds( ))可以进行等待!

#include <iostream>           // std::cout
#include <thread>             // std::thread
#include <mutex>              // std::mutex, std::unique_lock
#include <condition_variable> // std::condition_variable

std::mutex mtx;
std::condition_variable cv;
bool ready = false;

void print_id(int id) {
	std::unique_lock<std::mutex> lck(mtx);
	while (!ready) cv.wait(lck);
	// ...
	std::cout << "thread " << id << '\n';
}

void go() {
	std::unique_lock<std::mutex> lck(mtx);
	ready = true;
	cv.notify_all();
}

int main()
{
	//创建10个线程
	std::thread threads[10];
	// spawn 10 threads:
	for (int i = 0; i < 10; ++i)
		threads[i] = std::thread(print_id, i);

	std::cout << "10 threads ready to race...\n";
	//休眠保证所有线程进入wait
	this_thread::sleep_for(std::chrono::seconds( 1 ))
	go();                       // go!

	for (auto& th : threads) th.join();

	return 0;
}

这样进行的打印就是随机的了,因为调用的顺序不确定!
在这里插入图片描述

  1. 我们需要创建两个线程来分别执行打印奇数和打印偶数
  2. 一定要保证一个线程打印了一个 ,另一个线程才能打印一个!此时就是一个类似进程间通信的场景,为 false 打印偶数 , 为 true 打印奇数!每次打印都进行调整状态,帮助按照顺序进行打印!
  3. 条件变量的作用是在变量不符合条件时进行阻塞,等待变量才进行!
int main()
{
	mutex mtx;
	condition_variable c;
	int n = 100;
	//为 false 打印偶数
	//为 true 打印奇数
	bool flag = false;

	thread t1([&]()
		{
			int i = 0;
			while (i < n)
			{
				unique_lock<mutex> lock(mtx);
				//只要flag == false就进行阻塞 
				//防止连续打印
				while (flag)
					c.wait(lock);

				cout << i << endl;
				flag = true;

				i += 2;
				c.notify_one();
			}
		});

	thread t2([&]()
		{
			int i = 1;
			while (i < n)
			{
				unique_lock<mutex> lock(mtx);
				//只要flag == true就进行阻塞
				while (!flag)
					c.wait(lock);

				cout << i << endl;
				flag = false;

				i += 2;
				c.notify_one();
			}
		});

	t1.join();
	t2.join();

	return 0;
}

来看效果:
在这里插入图片描述
很顺利!这时两个线程的情况,如果有多个进程,可以通过宏定义一些数字,每个线程任务对应一个数字。变量满足时才进行执行任务!这样就会让不符合条件的变量阻塞在条件变量或者阻塞在获取锁中!通过这样的调控,可以满足多线程情况下的并发需求!

Thanks♪(・ω・)ノ谢谢阅读!!!

下一篇文章见!!!

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

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

相关文章

最短路问题中的bellman-ford算法

最短路问题中的bellman-ford算法 题目 如果要处理单源最短路问题当中存在负权边的&#xff0c;那么就需要用到 bellman-ford算法和SPFA算法&#xff0c;一般情况下都是用 SPFA算法&#xff0c;除了有边数限制的情况只能用bellman-ford算法&#xff0c;比如下面这种 题目 给定…

SVG入门指南

前言 SVG 是一种使用 XML 描述 2D 图形的语言&#xff0c;与传统前端 DOM 开发比较类似&#xff0c;本文尝试总结一下 SVG 的基本用法。 基本图形绘制 与 canvas 不同&#xff0c;svg 使用声明式的方式来绘制图形&#xff0c;比如绘制一个矩形&#xff1a; <svg width&q…

Elastic 利用由 Search AI 提供支持的自动导入功能加速 SIEM 数据导入

作者&#xff1a;来自 Elastic Jamie Hynds, Mark Settle Elastic 正在通过自动导入功能自动完成 SIEM 数据导入&#xff0c;从而加速采用 AI 驱动的安全分析。这项新功能&#xff08;安全分析或 SIEM 解决方案中独一无二的功能&#xff09;可自动开发自定义数据集成。Elastic …

轻松上手MYSQL:掌握MYSQL聚合函数,数据分析不再难

​&#x1f308; 个人主页&#xff1a;danci_ &#x1f525; 系列专栏&#xff1a;《设计模式》《MYSQL》 &#x1f4aa;&#x1f3fb; 制定明确可量化的目标&#xff0c;坚持默默的做事。 ✨欢迎加入探索MYSQL聚合函数之旅✨ &#x1f44b; 大家好&#xff01;文本学习和探…

240807-Gradio客户端GUI与服务端API分离代码示例

A. 客户端服务端分离的最终效果 B. 前后端核心代码 Gradio支持将前端界面&#xff08;客户端&#xff09;与后端逻辑&#xff08;服务端&#xff09;分离。通过这种方式&#xff0c;客户端负责用户交互和显示&#xff0c;而服务端负责实际的数据处理和功能实现。 以下是如何实…

买Zoho企业邮箱赠公司后缀的域名吗?

随着市场上企业邮箱服务提供商的增多&#xff0c;选择适合自身需求的服务变得尤为重要。其中一个常见的问题是&#xff1a;购买Zoho企业邮箱服务时是否会赠送公司后缀的域名&#xff1f;赠送域名有哪些套路&#xff1f;本文将详细探讨这些问题。 一、买Zoho企业邮箱赠送公司后…

让三岁小孩都能理解动态规划_来自B站罐装-蜜糖

系列文章目录 文章目录 系列文章目录一、认识算法动态规划难在哪?学习目标 二、记忆化搜索 非常直觉的处理方式注意&#xff1a; 三、70.爬楼梯 入门 模板通过记忆化搜索 发现动态规划四要素 四、118.杨辉三角 使用答案空间处理&#xff08;题目给了返回值的样式&#xff09;五…

The Ether: EvilScience (v1.0.1)打靶渗透【附代码】(权限提升)

靶机下载地址: https://www.vulnhub.com/entry/the-ether-evilscience-v101,212/ 1. 主机发现端口扫描目录扫描敏感信息获取 1.1. 主机发现 nmap -sn 192.168.7.0/24|grep -B 2 00:0C:29:7F:50:FB 1.2. 端口扫描 nmap -p- 192.168.7.172 1.3. 目录扫描 dirb http://192.16…

vm虚拟机下安装CentOS7系统

VMware16安装CentOS7 1.启动之前安装的VM 具体VMware安装过程 2.配置Linux&#xff08;centos7&#xff09;的镜像文件 选择安装镜像文件 4.开启虚拟机 开始读秒安装 选择安装过程中使用的语言&#xff0c;这里选择英文、键盘选择美式键盘。点击Continue 首先设置时间…

MySQL——数据类型、索引的建立、数据的约束

文章目录 数据类型索引的建立普通索引唯一索引使用ALTER 命令添加和删除索引使用ALTER 命令添加和删除主键显示索引信息 数据的约束非空约束&#xff1a;not null&#xff0c;值不能为null唯一约束&#xff1a;unique&#xff0c;值不能重复主键约束&#xff1a;primary key外键…

【实现100个unity特效之15】最简单的方法使用shader graphs实现2d非像素和像素树叶草的随风摇摆效果

文章目录 前言非像素树叶草飘动效果新建材质效果像素树叶草飘动效果参考完结 前言 本文只是实现一个简单版本的2d树叶草随风摇摆的效果&#xff0c;如果你想要实现更加复杂的效果&#xff0c;包括2d互动草&#xff0c;你可以参考我之前的文章&#xff1a; 【推荐100个unity插件…

基元检测-霍夫直线变换原理

在之前已经使用过正规方程法、梯度下降法拟合直线 1、回顾过去 梯度下降法拟合一元函数 最小二乘法的线性代数推导 现在使用一种新的直线检测方法&#xff0c;霍夫变换&#xff0c;它也可以拟合直线&#xff0c;接下里将说明他的原理。 2、霍夫变换 霍夫变换&#xff08;…

工地安全巡检系统

在建筑施工行业&#xff0c;安全始终是高悬的“达摩克利斯之剑”。随着科技的日新月异&#xff0c;工地安全二维码巡检系统崭露头角&#xff0c;成为守护工地安全的强大利器。 通过凡尔码平台&#xff0c;工地安全巡检系统融合了先进信息技术与安全管理理念的创新成果。其功能丰…

免费的录屏软件有哪些?试试这4款巨好用的录屏软件

4个免费简单实用的录屏软件推荐&#xff0c;帮你解决录屏问题。 1、转转大师录屏软件 下载链接>>screencap.55.la 这是一款非会员录屏也不限制时长的免费录屏软件&#xff0c;操作简单&#xff0c;第一次用就会&#xff0c;必须五星推荐给你&#xff01; 支持多种录制模…

数据结构——排序(1):插入排序

目录 一、排序的概念 二、排列的运用 三、常见的排序算法 四、插入排序 1.直接插入排序 &#xff08;1&#xff09;思路 &#xff08;2&#xff09;过程图示 &#xff08;3&#xff09;代码实现 (4)代码解释 &#xff08;5&#xff09;特性 2.希尔排序 &#xff08;1…

【Java算法专场】位运算(上)

目录 常见位运算总结 位1的个数 算法思路 算法代码 比特位计数 算法思路 算法代码 汉明距离 算法思路 算法代码 只出现一次的数字 算法思路 算法代码 丢失的数字 算法思路 算法代码 常见位运算总结 了解位运算的一些基本操作&#xff0c;那么我们就来通过题目来…

STM32的USB接口介绍

STM32 USB接口是STM32微控制器系列中集成的一种通信接口&#xff0c;它允许STM32微控制器与外部设备或计算机进行高速的数据传输和通信。以下是STM32 USB接口的简要介绍&#xff1a; 1. 接口类型 STM32的USB接口通常支持USB 2.0标准&#xff0c;部分高端型号可能还支持USB 3.…

新手必看!剪映轻松上手,让你的视频瞬间高大上

相信现在短视频兴起的时代下&#xff0c;几乎人手都在学习如何剪辑出日常视频&#xff0c;尤其是想要走新媒体路线的小伙伴更是在尝试专业的剪辑&#xff0c;不过平时的vlog或者抖音短视频可以从简单的开始接触&#xff0c;剪映是其中的一款适合初学者上手的剪辑了&#xff0c;…

网络编程复习

1.网络编程基础 1.1引入 socket套接字实现主机之间的通信 cs通信模型基于socket实现&#xff0c;需要客户端软件来实现通信 bs通信模型基于http实现&#xff0c;是网页通信&#xff0c;不需要任何客户端软件 1.2通信协议 &#xff08;1&#xff09;OSI七层通信协议&#xff…

QEMU理解与分析系列(1):QEMU简介

QEMU简介 一、QEMU基本介绍1.1操作模式1.2 虚拟化方式中间代码实现方式简介源码结构分布 二、qemu tcg前端解码逻辑2.1 tcg翻译流程2.1.1 decode tree语法2.1.2 trans_xxx函数的逻辑 三、编译相关3.1 代码拉取&#xff08;拉取自己想要的版本&#xff09;3.2 编译参数3.3 依赖包…