C++进阶 多线程相关

本篇博客介绍: 主要介绍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的初始值来控制哪一个线程先行动

运行结果如下

在这里插入图片描述

总结

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

怎样做好数字营销呢?

2023 年&#xff0c;数字营销将随着新技术、趋势和消费者行为的不断发展而不断发展。要在 2023 年在数字营销领域取得成功&#xff0c;请考虑以下策略&#xff1a; 1.内容质量和个性化&#xff1a; 专注于制作与目标受众产生共鸣的高质量且相关的内容。 根据用户偏好、行为和…

【健康医疗】Axure用药提醒小程序原型图,健康管理用药助手原型模板

作品概况 页面数量&#xff1a;共 20 页 兼容软件&#xff1a;Axure RP 9/10&#xff0c;不支持低版本 应用领域&#xff1a;健康管理&#xff0c;用药助手 作品申明&#xff1a;页面内容仅用于功能演示&#xff0c;无实际功能 作品特色 本作品为「用药提醒」小程序原型图…

【VS】InstallerProjects.vsix下载 Microsoft Visual Studio Installer Projects (2022)

InstallerProjects.vsix 是微软官方提供的winform程序打包工具&#xff0c;但是国内下载安装有时候比较慢。虽然只有5m左右&#xff0c;但是国内就是下载不下来。现将官网地址和下载后的百度网盘共享地址展示如下&#xff1a;方便大家使用 官方地址&#xff1a;https://market…

[C++] string类常用接口的模拟实现

文章目录 1、前言2、遍历2.1 operator[ ]下标方式2.2 迭代器2.3 范围for2.4 c_str 3、容量相关3.1 size&#xff08;大小&#xff09;3.2 capacity&#xff08;容量&#xff09;3.3 empty&#xff08;判空&#xff09;3.4 clear&#xff08;清理&#xff09;3.5 reserve3.6 res…

生成模型 -- GAN

文章目录 1. 生成模型与判别模型1.1 生成模型 2. VAE3. GAN3.1 GAN-生成对抗网络3.2 GAN-生成对抗网络的训练3.2.1 判别模型的训练&#xff1a;3.2.2 生成网络的训练&#xff1a; 4. LeakyReLU5. GAN代码实例 1. 生成模型与判别模型 生成模型与判别模型 我们前面几章主要介绍了…

UE4 材质学习笔记

CheapContrast与CheapContrast_RGB都是提升对比度的&#xff0c;一个是一维输入&#xff0c;一个是三维输入&#xff0c;让亮的地方更亮&#xff0c;暗的地方更暗&#xff0c;不像power虽然也是提升对比度&#xff0c;但是使用过后的结果都是变暗或者最多不变&#xff08;值为1…

Mybatis简单入门

星光下的赶路人star的个人主页 夏天就是吹拂着不可预期的风 文章目录 1、Mybatis介绍1.1 JDBC痛点1.2 程序员的诉求1.3 Mybatis简介 2、数据准备2.1 数据准备2.2 建工程2.3 Employee类2.4 Mybatis的全局配置2.5 编写要执行的SQL2.6 编写java程序2.7 稍微总结一下流程 3、解决属…

什么是安全测试报告,怎么获得软件安全检测报告?

安全测试报告 软件安全测试报告&#xff1a;是指测试人员对软件产品的安全缺陷和非法入侵防范能力进行检查和验证的过程&#xff0c;并对软件安全质量进行整体评估&#xff0c;发现软件的缺陷与 bug&#xff0c;为开发人员修复漏洞、提高软件质量奠定坚实的基础。 怎么获得靠谱…

Hadoop学习一(初识大数据)

目录 一 什么是大数据&#xff1f; 二 大数据特征 三 分布式计算 四 Hadoop是什么? 五 Hadoop发展及版本 六 为什么要使用Hadoop 七 Hadoop vs. RDBMS 八 Hadoop生态圈 九 Hadoop架构 一 什么是大数据&#xff1f; 大数据是指无法在一定时间内用常规软件工具对其内…

昌硕科技、世硕电子同步上线法大大电子合同

近日&#xff0c;世界500强企业和硕联合旗下上海昌硕科技有限公司&#xff08;以下简称“昌硕科技”&#xff09;、世硕电子&#xff08;昆山&#xff09;有限公司&#xff08;以下简称“世硕电子”&#xff09;的电子签项目正式上线。上线仪式在上海浦东和硕集团科研大楼举行&…

渗透测试方法论

文章目录 渗透测试方法论1. 渗透测试种类黑盒测试白盒测试脆弱性评估 2. 安全测试方法论2.1 OWASP TOP 102.3 CWE2.4 CVE 3. 渗透测试流程3.1 通用渗透测试框架3.1.1 范围界定3.1.2 信息搜集3.1.3 目标识别3.1.4 服务枚举3.1.5 漏洞映射3.1.6 社会工程学3.1.7 漏洞利用3.1.8 权…

Java课题笔记~ SpringBoot基础配置

二、基础配置 1. 配置文件格式 问题导入 框架常见的配置文件有哪几种形式&#xff1f; 1.1 修改服务器端口 http://localhost:8080/books/1 >>> http://localhost/books/1 SpringBoot提供了多种属性配置方式 application.properties server.port80 applicati…

jmeter HTTP请求默认值

首先&#xff0c;打开JMeter并创建一个新的测试计划。 右键单击测试计划&#xff0c;选择"添加" > “配置元件” > “HTTP请求默认值”。 在HTTP请求默认值中&#xff0c;您可以设置全局的HTTP请求属性&#xff0c;例如&#xff1a; 服务器地址&#xff1a…

神经网络简单理解:机场登机

目录 神经网络简单理解&#xff1a;机场登机 ​编辑 激活函数&#xff1a;转为非线性问题 ​编辑 激活函数ReLU 通过神经元升维&#xff08;神经元数量&#xff09;&#xff1a;提升线性转化能力 通过增加隐藏层&#xff1a;增加非线性转化能力​编辑 模型越大&#xff0c;…

uniapp日期选择组件优化

<uni-forms-item label="出生年月" name="birthDate"><view style="display: flex;flex-direction: row;align-items: center;height: 100%;"><view class="" v-

【图论】最短路的传送问题

一.分层图问题&#xff08;单源传送&#xff09; &#xff08;1&#xff09;题目 P4568 [JLOI2011] 飞行路线 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) &#xff08;2&#xff09;思路 可知背景就是求最短路问题&#xff0c;但难点是可以使一条路距离缩短至0&#xf…

一、数学建模之线性规划篇

1.定义 2.例题 3.使用软件及解题 一、定义 1.线性规划&#xff08;Linear Programming&#xff0c;简称LP&#xff09;是一种数学优化技术&#xff0c;线性规划作为运筹学的一个重要分支&#xff0c;专门研究在给定一组线性约束条件下&#xff0c;如何找到一个最优的决策&…

绿盾客户端字体库文件被加密了,预览不了

环境: 绿盾客户端7.0 Win10 专业版 问题描述: 绿盾客户端字体库文件被加密了,预览不了 预览不了 解决方案 1.打开控制台 2.进入规则中心 3.找到对应的操作员类型 4.选择自定义程序 5.右键新建程序,填最开始获得的程序名,可执行程序选择本地程序,我本地没有这个…

pytest之parametrize参数化

前言 我们都知道pytest和unittest是兼容的&#xff0c;但是它也有不兼容的地方&#xff0c;比如ddt数据驱动&#xff0c;测试夹具fixtures&#xff08;即setup、teardown&#xff09;这些功能在pytest中都不能使用了&#xff0c;因为pytest已经不再继承unittest了。 不使用dd…

PHP8中自定义函数-PHP8知识详解

1、什么是函数&#xff1f; 函数&#xff0c;在英文中的单词是function&#xff0c;这个词语有功能的意思&#xff0c;也就是说&#xff0c;使用函数就是在编程的过程中&#xff0c;实现一定的功能。即函数就是实现一定功能的一段特定代码。 在前面的教学中&#xff0c;我们已…