C++进阶 多线程相关

news2025/1/16 16:04:25

本篇博客介绍: 主要介绍C++中的一些线程操作以及线程库

C++进阶 多线程相关

    • 为什么要有线程库
    • 线程库介绍
      • 线程库常见的接口
      • 构造线程对象
      • 获取线程id
      • join和deteach
    • mutex库
    • 原子操作相关
    • 条件变量库
    • 总结

为什么要有线程库

我们在Linux中写多线程的时候使用的是Linux下提供的多线程的一套api

但是如果我们的运行环境变成了windows呢 windows提供的多线程api肯定和Linux不同

也就是说 我的代码可移植性很差 这个时候如果有一个语言层面的库就能解决移植性的问题了

而在C++11中最重要的特性就是对线程进行支持了 使得C++在并行编程时不需要依赖第三方库 而且在原子操作中还引入了原子类的概念 要使用标准库中的线程 必须包含thread头文件

线程库介绍

线程库常见的接口

成员函数用处
thread()构造一个线程对象,没有关联任何线程函数,即没有启动任何线程
get_id()获取线程id
jionable()线程是否还在执行,joinable代表的是一个正在执行中的线程。
jion()该函数调用后会阻塞住线程,当该线程结束后,主线程继续执行
detach()在创建线程对象后马上调用,用于把被创建线程与线程对象分离开,分离的线程变为后台线程,创建的线程的"死活"就与主线程无关

构造线程对象

在这里插入图片描述

方式一 : 无参构造

函数如下

  thread();

这是一个无参构造 所以说创建出来之后并没有任何的任务与其相关联 如果我们需要使用移动构造赋予其一个任务

代码表示如下

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


int main()
{
	thread t1;
	t1 = thread(func, 10);
	t1.join();
	return 0;
}

方式二: 带参构造

函数如下

template <class Fn, class... Args>
explicit thread (Fn&& fn, Args&&... args);

此时我们只需要省略无参构造t1的过程 直接使用构造函数即可 代码表示如下

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


int main()
{
	thread t1(func, 10);
	t1.join();
	return 0;
}

方式三 拷贝构造

在C++中的thread库中 我们禁止了拷贝构造 在这里特地强调下

方式四 移动构造

函数如下

thread (thread&& x) noexcept;

使用方式和第一种无参构造很类似 代码表示如下

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


int main()
{
	thread t1 = thread(func, 10);
	t1.join();
	return 0;
}

获取线程id

函数原型如下

this_thread::get_id()

我们可以使用该函数来获取线程的id 代码和运行结果如下

void func(int n)
{
	cout << this_thread::get_id() << endl;
}


int main()
{
	thread t1 = thread(func, 10);
	t1.join();
	return 0;
}

在这里插入图片描述

join和deteach

thread库中提供给我们这两个函数的用途主要是让我们去回收我们的线程资源

join

当我们调用 join() 函数的时候主线程会被阻塞

当新线程终止的时候主线程回去回收对应的资源 代码和运行结果如下


void func(int n)
{
	cout << this_thread::get_id() << endl;
}


int main()
{
	thread t1 = thread(func, 10);
	t1.join();
	return 0;
}

在这里插入图片描述

注意点:

  • join的作用是回收资源 如果说我们连续对于一个地方回收两次资源则会造成程序崩溃

deteach

当我们调用 deteach() 函数的时候 就相当于主线程和新线程之间没有关系了

各跑各的

代码和示例如下

void func(int n)
{
	cout << this_thread::get_id() << endl;
}


int main()
{
	thread t1 = thread(func, 10);
	t1.join();
	Sleep(1);
	return 0;
}

在这里插入图片描述

注意点:

  • 我们在主线程的代码中如果不加sleep(1) 那么有可能新线程还没跑起来 整个进程就运行结束了

mutex库

不加锁会出现的问题

我们写出下面的代码 : 创建两个线程 让这两个线程执行同一个任务 打印0~99的数字

void func(int n)
{
	for (int i = 0; i < n; i++)
	{
		cout << this_thread::get_id() << " : " << i << endl;
	}
}


int main()
{
	thread t1 = thread(func, 100);
	thread t2 = thread(func, 100);
	t1.join();
	t2.join();
	return 0;
}

我们会发现打印的时候出现这种情况

在这里插入图片描述

这是因为我们没有给线程加锁 在一个线程运行到一半的时候时间到了 切换到另一个线程的输出

解决的方式就是通过加锁

我们首先在定义一个锁 mtx

mutex mtx;

并且在任务开始之前加锁 任务开始之后解锁

	mtx.lock();
	for (int i = 0; i < n; i++)
	{
		cout << this_thread::get_id() << " : " << i << endl;
	}
	mtx.unlock();

线程运行就不会出现上面的问题了

在这里插入图片描述

C++线程库的引用问题

在C++的线程库当中 我们不能直接使用引用传递参数 否则一些编译器会报错 (博主使用的vs2022) 一些编译器虽然编译通过了 但是没有引用的效果 (我们老师使用vs2019出现上述效果)

如果说我们要往线程调用函数中传引用 则我们需要使用到这样的一个函数

ref(value)

代码和表示结果如下

void func(int n , int& x)
{
	for (int i = 0; i < n; i++)
	{
		mtx.lock();
		cout << this_thread::get_id() << " : " << i << endl;
		x++;
		mtx.unlock();
	}

}


int main()
{
	int x = 0;
	thread t1 = thread(func, 10,  ref(x));
	thread t2 = thread(func, 10,  ref(x));
	t1.join();
	t2.join();

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

在这里插入图片描述

当然 我们使用全局锁是很不安全的 并且会污染命名空间 所以说最好我们把锁也定义在main函数当中 并且以参数的形式 引用传递给函数

注意!我们不能使用传值的形式传递锁 因为它的拷贝构造函数被禁用了

演示结果如下
在这里插入图片描述

函数指针替换

我们在线程传参的时候不光可以传函数指针 还可以传递仿函数和lamabda表达式(底层就是仿函数)等等

有关于lamadba表达式的内容大家可以参考我的这篇博客

lamabda表达式

原子操作相关

关于原子性的相关问题 我在Linux线程互斥中详细介绍了 大家可以参考我的这篇博客

Linux线程互斥

如果大家阅读完了上面一篇博客之后就会知道 x++; 这并不是一个原子操作

要保证我们一个操作是原子的 除了加锁之外我们还可以使用C++提供的一个原子类

它的类名叫做 atomic

我们可以这么定义一个变量

atomic<int> x

此时我们使用 x++ 就是一个原子操作了 也就不需要互斥锁了

为什么我们使用atomic之后x++就变成原子操作了呢?

这里其实和我们mysql当中的事务很类似

具体可以参考我的这篇博客

mysql事务

它将x++底层的三条汇编语言封装成了一个整体 要么成功 要么失败 (失败之后就回滚)

靠着事务保证了原子性

注意:

  • 由于我们要保证原子性操作的资源一般是临界资源 所以说是不允许拷贝的 所以说atomic类中禁用了拷贝构造 移动构造等函数

条件变量库

学习到这个阶段我们应该有了一定自主学习的能力了

我们这里只介绍几个常用的函数 如果大家想深入学习以后可以直接在cspp网站上搜索函数学习

现在要求我们实现两个线程交替打印1-100

尝试用两个线程交替打印1-100的数字,要求一个线程打印奇数,另一个线程打印偶数,并且打印数字从小到大依次递增。

我们尝试使用上面学过的知识去做这道题

代码和表示结果如下

void func(mutex& mtx)
{
	for (int i = 0; i < 100; i+=2)
	{
		mtx.lock();
		cout << this_thread::get_id() << " : " << i << endl;
		mtx.unlock();
	}
}

void func2(mutex& mtx)
{
	for (int i = 1; i < 100; i += 2)
	{
		mtx.lock();
		cout << this_thread::get_id() << " : " << i << endl;
		mtx.unlock();
	}
}


int main()
{
	mutex mtx;
	thread t1 = thread(func ,ref(mtx));
	thread t2 = thread(func2, ref(mtx));
	t1.join();
	t2.join();

	return 0;
}

在这里插入图片描述

我们发现 虽然我们能够让两个线程分别打印奇数和偶数 但是却不能让它们依次执行

在解决了原子性之后就该我们的条件变量库上场了

我们一般会这样子定义一个条件变量

condition_variable cv;

然后我们用两个函数来使用它

	
template <class Predicate>
  void wait (unique_lock<mutex>& lck, Predicate pred);

参数说明:

  • 第一个参数是一个互斥锁 我们使用之前定义的锁构造一个就可以
  • 第二个参数是一个函数指针 它返回一个bool类型的参数 可以被反复调用 直到返回true为止

在线程进入等待状态之后会主动释放锁

void notify_one() noexcept;

我们可以使用该函数来唤醒处于等待状态的一个线程 唤醒处于等待状态的线程之后会自动获取锁

那么使用上面学的条件变量我们就可以修改我们的代码 使它完成功能

condition_variable cv;
bool flag = true;
bool Flag()
{
	return flag;
}

bool UFlag()
{
	return !flag;
}


void func(mutex& mtx)
{
	unique_lock<mutex> lck(mtx);
	for (int i = 0; i < 100; i+=2)
	{
	
		cv.wait(lck, Flag);
		cout << this_thread::get_id() << " : " << i << endl;

		flag = false;
		cv.notify_one();
	}
}

void func2(mutex& mtx)
{
	unique_lock<mutex> lck(mtx);
	for (int i = 1; i < 100; i += 2)
	{
	
		cv.wait(lck, UFlag);
		cout << this_thread::get_id() << " : " << i << endl;
	
		flag = true;
		cv.notify_one();
	}
}


int main()
{
	mutex mtx;
	thread t1 = thread(func ,ref(mtx));
	thread t2 = thread(func2, ref(mtx));
	t1.join();
	t2.join();

	return 0;
}

解释下上面的代码

  • 首先我们创造一个unique_lock 对象后它会自动调用lock()函数加锁
  • 当我们调用wait()函数的时候会自动解锁
  • 当我们调用notify_one()函数的时候会自动加锁

此外我们可以通过控制flag的初始值来控制哪一个线程先行动

运行结果如下

在这里插入图片描述

总结

在这里插入图片描述

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

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

相关文章

Windows使用MobaXterm远程访问ubuntu20.04桌面

参考ubuntu 2020.4 安装vnc 一、脚本文件 remote_setup.sh脚本文件内容&#xff1a; #! /bin/bash #参考链接&#xff1a;https://blog.csdn.net/hailangdeyingzi/article/details/124507304 sudo apt update sudo apt install x11vnc -y sudo x11vnc -storepasswd telpo.12…

【论文阅读】POIROT:关联攻击行为与内核审计记录以寻找网络威胁(CCS-2019)

POIROT: Aligning Attack Behavior with Kernel Audit Records for Cyber Threat Hunting CCS-2019 伊利诺伊大学芝加哥分校、密歇根大学迪尔伯恩分校 Milajerdi S M, Eshete B, Gjomemo R, et al. Poirot: Aligning attack behavior with kernel audit records for cyber thre…

Apache Paimon 实时数据湖 Streaming Lakehouse 的存储底座

摘要&#xff1a;本文整理自阿里云开源大数据表存储团队负责人&#xff0c;阿里巴巴高级技术专家李劲松&#xff08;之信&#xff09;&#xff0c;在 Streaming Lakehouse Meetup 的分享。内容主要分为四个部分&#xff1a; 流计算邂逅数据湖 Paimon CDC 实时入湖 Paimon 不止…

关于打包多模块SpringBoot项目并通过宝塔上传服务器

打包 —— 如何打包多模块项目&#xff0c;参考b站up主&#xff1a;[喜欢编程的代先生] 的视频 总结&#xff1a;1. 对着视频里看一下父模块和各个子模块pom.xml文件关于打包工具的依赖是否正确。2. 从最底层开始打包&#xff0c;逐层向上&#xff0c;最后再合并打包。 部署 …

网络安全(大厂)面试题

以下为网络安全各个方向涉及的面试题&#xff0c;星数越多代表问题出现的几率越大&#xff0c;祝各位都能找到满意的工作。 注&#xff1a;本套面试题&#xff0c;已整理成pdf文档&#xff0c;但内容还在持续更新中&#xff0c;因为无论如何都不可能覆盖所有的面试问题&#xf…

采购合同有哪些类型?应注意哪些内容?

采购合同为建立和管理客户与供应商的关系奠定了基础。在合同中&#xff0c;卖方同意向买方提供符合特定规格的用品&#xff0c;或承担买方的项目&#xff0c;并提供一个确定的价格&#xff08;通常有批量折扣&#xff09;。 作为回报&#xff0c;买方同意接收并支付一定数量的…

在ubuntu上部署label-studio

1. 安装label-studio 由于服务器的默认python3版本太低&#xff0c;尝试了很多方法&#xff0c;没有升级。因此采用annaconda方式安装label-studio. a.安装anaconda: 参照如下链接&#xff0c;安装anaconda。 Ubuntu安装Anaconda详细步骤&#xff08;Ubuntu22.04.1&#xff…

sql developer 连不上oracle数据库 报错 ORA-01031: insufficient privileges

sql developer 连不上oracle数据库 报错 ORA-01031: insufficient privileges 1、问题描述2、问题原因3、解决方法4、sql developer 连接oracle 成功 1、问题描述 使用sys账户以SYSDBA角色登录失败 报错 ORA-01031: insufficient privileges 2、问题原因 因为没有给sys账户分…

如何用Dockerfile部署LAMP架构

目录 构建LAMP镜像&#xff08;Dockerfile&#xff09; 第一步 创建工作目录 第二步 编写dockerfile文件 Dockerfile文件配置内容 第三步 编写网页执行文件 第四步 编写启动脚本 第五步 赋权并且构建镜像 第六步 检查镜像 第七步 创建容器 第八步 浏览器测试 构建LA…

Vue Cli 脚手架安装

Vue Cli 脚手架安装 首先&#xff0c;改一下仓库地址&#xff0c;使用下面的命令cnpm淘宝镜像加速 npm install cnpm -g --registryhttps://registry.npm.taobao.org下载安装 vue 脚手架 npm install -g vue/cli查看 vue cli 脚手架是否安装成功&#xff0c;如果输入命令出现…

服务机器人,正走向星辰大海

大数据产业创新服务媒体 ——聚焦数据 改变商业 国内机器人联盟&#xff08;IFR&#xff09;将机器人划分为工作机器人、服务机器人、特种机器人三类。服务机器人广泛应用于餐饮场景、酒店场景&#xff0c;早已构成一道靓丽的风景。行业数据显示&#xff0c; 作为服务机器人发…

想解锁禁用的iPhone?除了可以使用电脑之外,这里还有不需要电脑的方法!

多次输入错误的密码后,iPhone将显示“iPhone已禁用”。这种情况看起来很棘手,因为你现在不能用iPhone做任何事情。对于这种情况,我们提供了几种有效的方法来帮助你在最棘手的问题中解锁禁用的iPhone。你可以选择使用或不使用电脑来解锁禁用的iPhone。 一、为什么你的iPhone…

字节码调教的入口 —— JVM 的寄生插件 javaagent 那些事

Java Instrumentation 包 Java Instrumentation 概述 Java Instrumentation 这个技术看起来非常神秘&#xff0c;很少有书会详细介绍。但是有很多工具是基于 Instrumentation 来实现的&#xff1a; APM 产品: pinpoint、skywalking、newrelic、听云的 APM 产品等都基于 Instru…

【C++习题集】-- day6(习题)

目录 选择题 编程题 36932-求最小公倍数⭐ 【解题思路】 选择题 1. 执行下面语句后的输出为 ( ) int I1; if(I<0)printf("****\n") ; elseprintf("%%%%\n"); A、%% B、**** C、有语法错&#xff0c;不能正确执行 D、%%%% 正确答案&#xff1a;A …

Django基础2——URL路由系统

文章目录 一、基本了解二、url路由分发三、正则匹配四、压缩归档超链接优化一&#xff1a;使用分组名称功能优化二&#xff1a;使用url名称功能4.1 使用功能之前效果展示4.2 使用功能之后效果展示 一、基本了解 概念&#xff1a; 路由系统就是URL路径和视图函数的一个对应关系&…

【数据分析】波士顿矩阵

波士顿矩阵是一种用于分析市场定位和企业发展战略的管理工具。由美国波士顿咨询集团&#xff08;Boston Consulting Group&#xff09;于1970年提出&#xff0c;并以该集团命名。 波士顿矩阵主要基于产品生命周期和市场份额两个维度&#xff0c;将企业的产品或业务分为四个象限…

工具--录屏软件

记录下录屏软件 ScreenToGif 官网 &#xff1a;https://www.screentogif.com/downloads 我下载的是 Installer 版本。 录屏&#xff0c;默认输出为 gif 。录制的 gif 清晰&#xff0c;且容量低。需要录gif的话主推&#xff01; 录制后输出为 mp4 的话提示要下载 FFmpeg &a…

基于 Alpine 环境源码构建 alibaba-tengine(阿里巴巴)的 Docker 镜像

About Alpine&#xff08;简介&#xff09; Alpine Linux 是一款极其轻量级的 Linux 发行版&#xff0c;基于 busybox&#xff0c;多被当做 Docker 镜像的底包&#xff08;基础镜像&#xff09;&#xff0c;在使用容器时或多或少都会接触到此系统&#xff0c;本篇文章我们以该镜…

ubuntu pdf阅读器okular

sudo apt-get install okular安装完毕后&#xff0c;使用如下命令浏览pdf文档 okular xxx.pdf

基于FPGA的FIR低通滤波器实现(附工程源码),matlab+vivado19.2+simulation

基于FPGA的FIR低通滤波器实现(附工程源码) 文章目录 基于FPGA的FIR低通滤波器实现(附工程源码)前言一、matlab设计FIR滤波器&#xff0c;生成正弦波1.设计FIR滤波器1.生成正弦波.coe 二、vivado1.fir滤波器IP核2.正弦波生成IP核3.时钟IP核设置4.顶层文件/测试文件代码 三.simul…