C++多线程的Demo(二)

news2024/12/24 10:18:00

前言

        接上文,这次对C++多线程和并发有了一些粗浅的理解,上一篇文章如下:

C++多线程的Demo(一)_c++ demo-CSDN博客

 详细讲解join()和detach():

        每一个程序至少拥有一个线程,那就是执行main()函数的主线程,而多线程则是出现两个或两个以上的线程并行运行,即主线程和子线程在同一时间段同时运行。而在这个过程中会出现几种情况:

        主线程先运行结束
        子线程先运行结束
        主子线程同时结束


        在一些情况下需要在子线程结束后主线程才能结束,而一些情况则不需要等待,但需注意一点,并不是主线程结束了其他子线程就立即停止,其他子线程会进入后台运行!!!这一点很多人根本发不现,包括原来的自己,以为主线程停了,子线程就自己消失了,其实并不是。在C++层面,子线程仍然在运行,只是对于不同的操作系统,会有一些优化,所以子线程会被优化掉,但是为了跨平台和安全性,还是应该注意。(当然,如果是detach的,C++运行时库也会在主线程退出以后,正确回收相关的资源,但是这不是我们自己不管理的理由)

join()示例

        join()函数是一个等待线程完成函数,主线程需要等待子线程运行结束了才可以结束。这样操作比较安全,但是有个致命的问题是,一定要注意join()的位置。如果在创建子线程以后,立马join(),然后再跟上主线程的任务,那么join()以后,程序会等待子线程执行,然后再执行主线程的任务,那么无非就是把主线程阻塞住,然后去执行子线程的部分任务,那这样和把所有任务都交给主线程有啥区别吗?多此一举不是?例如以下:

#include <iostream>
#include <thread>
using namespace std;

void func()
{
   for(int i = -10; i > -20; i--)
    {
        cout << "from func():" << i << endl;
    }
}

int main()			//主线程
{
	cout << "mian()" << endl;
    cout << "mian()" << endl;
    cout << "mian()" << endl;
	thread t(func);	//子线程
	t.join();		//等待子线程结束后才进入主线程
    cout << "mian()" << endl;
    cout << "mian()" << endl;
    cout << "mian()" << endl;
	return 0;
}

所以,为了能够保证主线程和子线程确实是分开执行的,应该把join()放到尽可能靠后的位置:

#include <iostream>
#include <thread>
using namespace std;

void func()
{
	for(int i = 10; i > 0; i--)
	{
		cout << "from func():" << i << endl;
		_sleep(1);
	}
}

int main()			//主线程
{
	thread t(func);	//子线程
	for(int i = 0; i < 10; i++)
	{
		cout << "mian():" << i << endl;
		_sleep(1);
	}
	t.join();		//等待子线程结束
	return 0;
}

detach()示例

detach()称为分离线程函数,使用detach()函数会让线程在后台运行,即说明主线程不会等待子线程运行结束才结束。通常称分离线程为守护线程(daemon threads),UNIX中守护线程是指,没有任何显式的用户接口,并在后台运行的线程。这种线程的特点就是长时间运行;线程的生命周期可能会从某一个应用起始到结束,可能会在后台监视文件系统,还有可能对缓存进行清理,亦或对数据结构进行优化。

#include <iostream>
#include <thread>
using namespace std;

void func()
{
	for(int i = 10; i > 0; i--)
	{
		cout << "from func():" << i << endl;
		_sleep(1);
	}
}

int main()			//主线程
{
	thread t(func);	//子线程
	t.detach();		//分离子线程
	for(int i = 0; i < 10; i++)
	{
		cout << "mian():" << i << endl;
		_sleep(1);
	}
	
	return 0;
}

这时候我们发现,在创建完子线程以后,直接detach()的话,和join想要的结果相同。

注意1:无限循环的子线程问题

        需要注意的是,以上这个例子是一个很快就执行完的子线程任务,如果是一个无限循环的任务,就不一定了,既然是分离,那么主线程关闭与否,并不影响子线程的运行状态。虽然在某些情况下,操作系统可能会在主线程结束后清理所有相关的资源,包括子线程。但是,这并不是C++标准所保证的行为,它完全依赖于操作系统的实现。

        所以,对于这种情况,建议使用join()来等待子线程结束,或者在子线程中设置某种形式的退出条件,以便它可以检测到主线程已经结束,并相应地结束自己的执行。

        使用标志位去控制的方法如下:

#include <iostream>
#include <thread>
using namespace std;

void func(bool & flag)    // 这里使用引用传递
{
	int i=0;
	while(flag)
	{
		cout << "from func():" << i++ << endl;
		_sleep(100);
	}
	cout << "from func() end!!!" << endl;
}

int main()			//主线程
{
	bool f = true;
	thread t(func, std::ref(f));	//子线程
	t.detach();			//分离子线程
	for(int i = 0; i < 50; i++)
	{
		cout << "mian():" << i << endl;
		_sleep(100);
	}
	f=false;
	_sleep(100);    // 最后需要等待100ms,不然的话,子线程无法执行end那句话
	return 0;
}

注意2:主线程报错退出的问题

        我们想象这么一种情况,就是子线程运行的好好的,也设置了停止条件,但是主线程里面,如果执行某个语句报错了咋办嘞? 就无法运行到那个标志位改变了,这样子线程也是停不了的:

#include <iostream>
#include <thread>
#include <exception>
using namespace std;

void func(bool & flag)	// 这里使用引用
{
	int i=0;
	while(flag)
	{
		cout << "from func():" << i++ << endl;
		_sleep(100);
	}
	cout << "from func() end!!!" << endl;
}

int main()			//主线程
{
	bool f = true;
	thread t(func, std::ref(f));	//子线程,传递参数
	t.detach();						//分离子线程
	for(int i = 0; i <50 ; i++)
	{
		cout << "mian():" << i << endl;
		if(i==25) throw std::runtime_error("An error occurred!");	// 手动抛出异常
		_sleep(100);
	}
	f=false;
	_sleep(100);		// 最后需要等待100ms,不然的话,子线程无法执行end那句话
	return 0;
}

解决办法就是加一个try 和catch:

伪代码如下:

thread t(func, std::ref(f));	//子线程,传递参数
try{
    do_something_in_current_thread();
}
catch(...)
{
    t.join();	  //关闭子线程
    flag = false; //标志位置为关
    throw;        //还是抛出异常退出
}

t.join();         //正常退出子线程
flag = false;     //标志位置为关

当然,使用detach()也行,就是改变一下位置即可。代码如下:

#include <iostream>
#include <thread>
#include <exception>
using namespace std;

void func(bool & flag)	// 这里使用引用
{
	int i=0;
	while(flag)
	{
		cout << "from func():" << i++ << endl;
		_sleep(100);
	}
	cout << "from func() end!!!" << endl;
}

int main()			//主线程
{
	bool f = true;
	thread t(func, std::ref(f));	//子线程,传递参数
	t.detach();						//分离子线程
	try{
		for(int i = 0; i <50 ; i++)
		{
			cout << "mian():" << i << endl;
			if(i==25) throw std::runtime_error("An error occurred!");	// 手动抛出异常
			_sleep(100);
		}
	}
	catch(...)
	{
		cout<<"something error!!!"<<endl;
		f=false;
		_sleep(100);
        //throw;        // 如果要保证程序正常报错的话,建议还是加上这段话
	}
	
	f=false;
	_sleep(100);		// 最后需要等待100ms,不然的话,子线程无法执行end那句话
	return 0;
}

        可以看到,顺利退出子线程了,但是这么写,如果只是一个的话,还好,如果有多个都可能报错嘞? 那岂不是得写一大堆try ... catch()... ? 这个嘛,目前还在思考有没有解决方法,如果有解决方法的话,后期会补充。

注意3:传递参数使用引用

一般情况下,传递参数很简单,例如:

#include <iostream>
#include <thread>
#include <string>
#include <exception>
using namespace std;

void func(int flag = 10, string s = string())	
{
	int i=0;
	while(flag)
	{
		cout << "from func():"<<s.c_str() << flag-- << endl;
		_sleep(10);
	}
	cout << "from func() end!!!" << endl;
}

int main()							//主线程
{
	bool f = true;
	int  a = 100;
	// thread t(func, 100);
	thread t(func, a ,"test");				//子线程,传递参数
	t.detach();						//分离子线程

	for(int i = 0; i <20 ; i++)
	{
		cout << "mian():" << i << endl;
		_sleep(100);
	}
	
	cout<<"Main end"<<endl;

	return 0;
}



但是如果传递的是引用的话,需要注意,如果传递的是一个函数内部的局部变量,多线程引用了此变量,在函数执行以后,子线程如果再使用此变量容易引发未定义的问题。一般来说,可以使用const &引用模式,然后转为右值以后再传递。比如指针指针unique_ptr,不能复制,只能移动,就需要使用move语句转为右值传递。

使用RAII实现线程管理

使用ThreadGuard类的析构函数来将标志位改变。

#include <iostream>
#include <thread>
#include <exception>
using namespace std;

class ThreadGuard {
	std::thread& t;
	bool &flag; 
public:
	//ThreadGuard(std::thread& t_):t(t_) {}		// 没有标志位就使用这个
	ThreadGuard(std::thread& t_, bool& flag_):t(t_),flag(flag_) {}
	
	~ThreadGuard() {
		cout<<"~ThreadGuard()"<<endl;
		flag = false;
		_sleep(100);		// 最后需要等待100ms,不然的话,子线程无法执行end那句话
		//if(t.joinable())	// 如果不是detach(),就这么写
		//	t.join();
	}
	ThreadGuard(ThreadGuard const&) = delete;				//拷贝构造
	ThreadGuard& operator=(ThreadGuard const&) = delete;	//赋值运算符
};

void func(bool & flag)	// 这里使用引用
{
	int i=0;
	while(flag)
	{
		cout << "from func():" << i++ << endl;
		_sleep(100);
	}
	cout << "from func() end!!!" << endl;
}

int main()			//主线程
{
	bool f = true;
	thread t(func, std::ref(f));	//子线程,传递参数
	t.detach();						//分离子线程
	
	ThreadGuard *tg = new ThreadGuard(t,f);
	for(int i = 0; i <50 ; i++)
	{
		cout << "mian():" << i << endl;
		//if(i==25) throw std::runtime_error("An error occurred!");	// 手动抛出异常
		_sleep(100);
	}
	
	delete tg;

	return 0;
}

这里需要注意的是,其实不使用指针,使用变量也可以,不过可以使用大括号来控制,因为离开了大括号的范围,就会自动调用析构函数,例如我这里使用do  while的大括号:

#include <iostream>
#include <thread>
#include <exception>
using namespace std;

class ThreadGuard {
	std::thread& t;
	bool &flag; 
public:
	//ThreadGuard(std::thread& t_):t(t_) {}		// 没有标志位就使用这个
	ThreadGuard(std::thread& t_, bool& flag_):t(t_),flag(flag_) {}
	
	~ThreadGuard() {
		cout<<"~ThreadGuard()"<<endl;
		flag = false;
		_sleep(100);		// 最后需要等待100ms,不然的话,子线程无法执行end那句话
		//if(t.joinable())	// 如果不是detach(),就这么写
		//	t.join();
	}
	ThreadGuard(ThreadGuard const&) = delete;				//拷贝构造
	ThreadGuard& operator=(ThreadGuard const&) = delete;	//赋值运算符
};

void func(bool & flag)	// 这里使用引用
{
	int i=0;
	while(flag)
	{
		cout << "from func():" << i++ << endl;
		_sleep(100);
	}
	cout << "from func() end!!!" << endl;
}

int main()			//主线程
{
	bool f = true;
	thread t(func, std::ref(f));	//子线程,传递参数
	t.detach();						//分离子线程
	
	do
	{
		ThreadGuard tg(t,f);
		for(int i = 0; i <50 ; i++)
		{
			cout << "mian():" << i << endl;
			if(i==25) break;	// 手动中级退出
			_sleep(100);
		}
	}while(false);
	
	cout<<"Main end"<<endl;

	return 0;
}

会发现在中途退出了。所以可见,使用这种方法,可以使用大括号来控制。当然,直接使用大括号也是可以的,不是一定要使用do while的大括号。

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

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

相关文章

python 画图|三维散点图输出

【1】引言 在前述学习进程中&#xff0c;已经初步掌握三维动画输出和散点图动画输出基本技能&#xff0c;可通过下述链接直达&#xff1a; python画图|散点图动态输出-CSDN博客 python动画教程|Animations using Matplotlib-官网教程程序解读_如何用python制作微动画-CSDN博…

【实时计算 Flink】DataStream作业大状态导致反压的调优原理与方法

状态管理不仅影响应用的性能&#xff0c;还关系到系统的稳定性和资源的有效利用。如果状态管理不当&#xff0c;可能会导致性能下降、资源耗尽&#xff0c;甚至系统崩溃。Flink Datastream API在状态管理方面提供了非常灵活的接口&#xff0c;您可以采取相关措施来确保状态大小…

Word粘贴时出现“文件未找到:MathPage.WLL”的解决方案

解决方案 一、首先确定自己电脑的位数&#xff08;这里默认大家的电脑都是64位&#xff09;二、右击MathType桌面图标&#xff0c;点击“打开文件所在位置”&#xff0c;然后分别找到MathPage.WLL三、把这个文件复制到该目录下&#xff1a;C:\Program Files\Microsoft Office\r…

Ubuntu 详解| Ubuntu ssh| Ubuntu apt命令大全| Ubuntu性能优化| Ubuntu换镜像源

Ubuntu 是Debian开源linux系统体系下的子分支之一 Debian-ubuntu 和它一样的还有 kali&#xff08;一款渗透测试软件&#xff09; Debian-kali 小白参考 &#xff1a;Centos 7.9 安装 图解版 小白必看 最新_centos7.9-CSDN博客文章浏览阅读2.5k次&#xff0c;点赞…

vue3.0 + vite:中使用 sass

1、安装依赖 npm i sass sass-loader --save-dev 在项目的src/assets文件夹下新建style/index.scss 文件 2、在 vite.config.ts 中加&#xff1a; resolve: {alias: {: fileURLToPath(new URL(./src, import.meta.url))} }, css: {// 配置 SCSS 支持preprocessorOptions: {s…

Mybatis Plus连接使用ClickHouse也如此简单

通过阅读列式数据库ClickHouse官网&#xff0c;不难看出它有支持JDBC规范的驱动jar包&#xff0c;可以直接集成到Object Relational Mapping框架等&#xff0c;下面我用SpringBootMybatisPlus环境连接ClickHouse来演示一下 集成步骤 1.Maven引入ClickHouse提供的JDBC依赖 <…

解决海外社媒风控问题的工具——云手机

随着中国企业逐步进入海外市场&#xff0c;海外社交媒体的风控问题严重影响了企业的推广效果与账号运营。这种背景下&#xff0c;云手机作为一种新型技术解决方案&#xff0c;正日益成为企业应对海外社媒风控的重要工具。 由于海外社媒的严格监控&#xff0c;企业经常面临账号流…

linux驱动-platform子系统

目录 1.传统字符设备驱动框架的缺点 2.总线、设备、驱动 3.设备注册 4.驱动注册 1.传统字符设备驱动框架的缺点 缺点&#xff1a;驱动可移植性差&#xff0c;原因是驱动里面包括了很多该芯片特有的消息&#xff0c;如果是其他平台&#xff0c;硬件信息会有差异&#xff0c;…

七、Linux 之用户管理

基本介绍 Linux 系统是一个多用户多任务的操作系统&#xff0c;任何一个要使用系统资源的用户&#xff0c;都必须首先向系统管理员申请一个账号&#xff0c;然后以这个账号的身份进入系统 添加用户 useradd 用户名添加一个用户 milan, 默认该用户的家目录在 /home/milan 细节说…

深入Postman- 自动化篇

前言 在前两篇博文《Postman使用 - 基础篇》《玩转Postman:进阶篇》中,我们介绍了 Postman 作为一款专业接口测试工具在接口测试中的主要用法以及它强大的变量、脚本功能,给测试工作人员完成接口的手工测试带来了极大的便利。其实在自动化测试上,Postman 也能进行良好的支…

【特赞-注册安全分析报告】

前言 由于网站注册入口容易被黑客攻击&#xff0c;存在如下安全问题&#xff1a; 暴力破解密码&#xff0c;造成用户信息泄露短信盗刷的安全问题&#xff0c;影响业务及导致用户投诉带来经济损失&#xff0c;尤其是后付费客户&#xff0c;风险巨大&#xff0c;造成亏损无底洞…

SQL分类中的DDL

DDL&#xff08;Data Definition Language):数据定义语言&#xff0c;用来定义数据库对象&#xff08;数据库&#xff0c;表&#xff0c;字段&#xff09;。 一、DDL语句操作数据库 1、查询所有数据库&#xff1a;show databases&#xff1b;&#xff08;一般用大写&#xff…

自动化——RPA——影刀——批量刷视频

前言 影刀底层是python https://www.yingdao.com/ 需求 实现视频集的定时点击和播放。 要点 第一步&#xff1a;将需要播放的视频链接放到excel中&#xff0c;excel的名字自定义&#xff0c;本文为“批量刷视频”。 第二步&#xff1a;使用影刀的“打开/新建Excel”功能&…

汽车免拆诊断案例 | 2022款大众捷达VS5车行驶中挡位偶尔会锁在D3挡

故障现象  一辆2022款大众捷达VS5汽车&#xff0c;搭载EA211发动机和手自一体变速器&#xff0c;累计行驶里程约为4.5万km。该车行驶中挡位偶尔会锁在D3挡&#xff0c;车速最高约50 km/h&#xff0c;且组合仪表上的发动机故障灯和EPC灯异常点亮。 故障诊断  用故障检测仪检…

Centos7快速安装配置RabbitMQ

1. 卸载现有的 RabbitMQ 和 Erlang&#xff08;可选&#xff09; # 停止 RabbitMQ 服务 sudo systemctl stop rabbitmq-server# 卸载 RabbitMQ sudo yum remove -y rabbitmq-server# 卸载 Erlang sudo yum remove -y erlang# 删除 RabbitMQ 残留文件 sudo rm -rf /var/lib/rab…

smartctl 设置硬盘的 write-caching

sg3 一、sg3查看缓存状态 您可以使用sg_modes命令来查看SAS盘和SATA盘的缓存状态。例如&#xff0c;要查看/dev/sdb设备的缓存状态&#xff0c;您可以执行以下命令&#xff1a; sg_modes -p 8,0 /dev/sdb 二、sg3关闭机械盘写缓存状态&#xff08;仅适用于SAS盘&#xff09…

ollama + fastgpt+m3e本地部署

ollama fastgptm3e本地部署 开启WSL更新wsl安装ubuntu docker下载修改docker镜像源开启WSL integration 安装fastgpt先创建一个文件夹来放置一些配置文件用命令下载fastgpt配置文件用命令下载docker的部署文件 启动容器M3E下载ollama下载oneapi配置登录oneapi配置ollama渠道配…

拥抱云原生

专题七&#xff1a;云原生实战72课时 专题简介&#xff1a; 云原生正在改变世界&#xff0c;新一代架构思想ServiceMesh、Serverless改变传统软件架构模式&#xff0c;本专题基于完全云上架构实战&#xff0c;结合微服务架构和云计算平台两者的优势&#xff0c;属于架构师必备…

LabVIEW智能可变温循环PCT测试系统

随着全球能源危机的加剧和环境保护需求的提升&#xff0c;开发和利用清洁能源已成为全球必然趋势。氢能作为一种高效的替代能源&#xff0c;正逐步受到关注。然而&#xff0c;储氢技术的研究至关重要&#xff0c;尤其是储氢材料的PCT&#xff08;Pressure-Composition-Temperat…

如何使用UART(STM32 HAL库)

UART &#xff08;通用异步收发器&#xff09;是在 USART &#xff08;通用同步异步收发器&#xff09;基础上裁剪掉了同步通信功能&#xff0c;只剩下异步通信功能。关于通信和串口的基本知识&#xff0c;可参见文章《串口通信简介-CSDN博客》和《数据通信的一些基础概念-CSDN…