C++多线程的Demo(二)

前言

        接上文,这次对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()... ?

使用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.xdnf.cn/news/1562076.html

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

相关文章

三步完成Llama3.2在算力魔方的INT4量化和部署|开发者实战

2024年9月25日&#xff0c;Meta又发布了Llama3.2&#xff1a;一个多语言大型语言模型&#xff08;LLMs&#xff09;的集合&#xff0c;其中包括&#xff1a; 大语言模型&#xff1a; 1B和3B参数版本&#xff0c;仅接收多种语言文本输入。多模态模型&#xff1a; 11B和90B参数版…

Asahi Linux通过大量变通方法实现在M系列Mac上支持AAA级游戏

如果您正在运行 Asahi Linux 并希望在您的 M 系列 Mac 上玩游戏&#xff0c;那么有一个好消息要告诉您&#xff0c;Asahi Linux 项目将继续推出新功能。 2 月份它在 Mac 上Apple Silicon 实现了OpenGL 4.6 和 OpenGL ES 3.2 兼容&#xff0c;现在又在游戏方面取得了进展。但您可…

JS 分支语句

目录 1. 表达式与语句 1.1 表达式 1.2 语句 1.3 区别 2. 程序三大流控制语句 3. 分支语句 3.1 if 分支语句 3.2 双分支 if 语句 3.3 双分支语句案例 3.3.1 案例一 3.3.2 案例二 3.4 多分支语句 1. 表达式与语句 1.1 表达式 1.2 语句 1.3 区别 2. 程序三大流控制语…

计算机毕业设计 基于Python+Django的旅游景点数据分析与推荐系统的设计与实现 Python毕业设计 Python毕业设计选题【附源码+安装调试】

博主介绍&#xff1a;✌从事软件开发10年之余&#xff0c;专注于Java技术领域、Python人工智能及数据挖掘、小程序项目开发和Android项目开发等。CSDN、掘金、华为云、InfoQ、阿里云等平台优质作者✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精…

国际数据安全领域新探索:天空卫士参加迪拜渠道大会

2024年9月24日&#xff0c;由中东及北非地区知名分销商Quantum Edge主办的渠道大会在迪拜隆重召开。此次会议汇聚了来自该地区数据安全领域的优秀渠道合作伙伴、技术专家、行业领袖及大学研究机构。天空卫士作为中国数据安全企业&#xff0c;积极参与本次盛会&#xff0c;与国际…

AI阅读文献,这个方法10倍速提升效率还不损失关键信息!

我是娜姐 迪娜学姐 &#xff0c;一个SCI医学期刊编辑&#xff0c;探索用AI工具提效论文写作和发表。 关于用AI快速读论文&#xff0c;之前娜姐分享过好几款工具&#xff0c;有浏览器插件Kimi、豆包&#xff0c;还有专门的AI工具&#xff0c;如ChatDoc、ChatPDF、SciSpace、Scit…

域名郵箱:注册流程详解及注意事项有哪些?

域名郵箱怎么申请创建&#xff1f;域名郵箱如何设置及优化策略&#xff1f; 域名郵箱已成为企业和个人展示专业形象的重要工具。与普通邮箱相比&#xff0c;域名郵箱不仅更具个性化&#xff0c;还能提升品牌信任度。烽火将详细介绍域名郵箱的注册流程及注意事项&#xff0c;帮…

css 翻页效果

有一个项目&#xff0c;页面切换的时候要翻页效果。 所以有一个简单的demo&#xff0c;提供给大家学习 <!DOCTYPE html> <html lang"en"> <head> <meta charset"UTF-8"> <meta name"viewport" content"widthdev…

移远通信受邀参展2024中国移动全球合作伙伴大会,以深厚实力全力迎接AI+时代

在中国科技迅速发展的今天&#xff0c;人工智能&#xff08;AI&#xff09;与物联网正在成为推动数字经济变革的重要力量。 为进一步推进AI技术与各领域的融合发展&#xff0c;10月11日至13日&#xff0c;第12届中国移动全球合作伙伴大会在广州市隆重举行&#xff0c;本次大会以…

关于安科瑞ABAT蓄电池在线监测系统的详细介绍-安科瑞 蒋静

蓄电池在线监测系统是一种用于实时监测蓄电池状态并分析其性能的重要设备。该系统通过监测蓄电池的关键参数&#xff0c;如电压、电流、温度、内阻等&#xff0c;对电池的性能和健康状况进行评估&#xff0c;从而及时发现潜在问题并采取相应的维护措施。以下是对蓄电池在线监测…

Linux下多任务编程(网络编程)

前言 本文记录OSI7层模型、TCP\IP模型、socket在UDP、TCP使用。 网络 网络&#xff1a;多个计算机之间相互通信 网络协议&#xff1a;多个计算机之间通信用的语言&#xff08;是有一定规范的&#xff09; OSI 7层模型 应用层 表示层 会话层 传输层 网络层 链路层 物理…

在数字电路实验的测试中,示波器的输入耦合为什么要选用直流耦合?

示波器输入耦合的作用及其在数字电路测试中的选择 概述 示波器是电子工程师进行各种信号测量和分析的关键工具。无论是在模拟电路还是数字电路中&#xff0c;示波器都能提供对信号状态的直观展示。然而&#xff0c;在使用示波器进行测量时&#xff0c;输入耦合的选择至关重要…

搭建Web环境、初识JSP

搭建Web环境、初识JSP 1.B/S架构工作原理 B/S架构采用请求/响应模式进行交互 2.URL 计算机通过统一资源定位符实现资源访问 URL&#xff1a;Uniform Resource Locator的缩写 唯一能识别Internet上具体的计算机、目录或文件夹位置的命名约定 3.Web服务器 Web服务器 是可以向…

电子木鱼解压小程序源码系统 带源代码包以及搭建部署教程 源码开源可二开

系统概述 电子木鱼解压小程序源码系统是一款基于现代Web技术开发的轻量级应用程序&#xff0c;旨在为用户提供一种简单、高效的解压方式。该系统通过模拟传统木鱼的敲击效果&#xff0c;结合优美的音效和动画&#xff0c;帮助用户达到放松身心的效果。同时&#xff0c;系统还提…

基于SSM的朋辈帮扶系统

文未可获取一份本项目的java源码和数据库参考。 一、本课题研究意义 随着市场经济发展的不断深入&#xff0c;高校学生面临着新的问题和挑战。在全球一体化、价值观多元化、信息网络化的大背景下&#xff0c;越来越多的学生承受着来自社会、家庭与自身方方面面的压力&#xf…

【Linux】嵌入式Linux系统的组成、u-boot编译

Linux—嵌入式Linux系统的组成、u-boot编译 前言一、嵌入式Linux系统的组成1.1 嵌入式Linux系统和PC完整的操作系统的对比如下&#xff1a;1.2 PC机—Windows系统启动流程&#xff08;PC机—Linux系统、嵌入式ARM—linux系统的启动流程类似&#xff09; 二、编译u-boot2.1 u-bo…

【动手学电机驱动】 TI InstaSPIN-FOC(2)Lab01 闪灯实验

【动手学电机驱动】 TI InstaSPIN-FOC&#xff08;2&#xff09;Lab01 闪灯实验 1. 硬件连接与设置2. Lab01&#xff1a;闪灯实验2.1 项目简介2.2 导入项目2.3 调试和运行 3. 修改程序&#xff1a;改变闪灯频率4. 程序解读4.1 软件流程图4.2 包含文件、变量和函数声明4.3 中断服…

Java Mail腾讯企业邮箱或其他邮箱发送邮件失败bug记录

问题出现情况 邮件发送时debug用F8逐步运行可以成功发送邮件&#xff0c;但是用F9或者直接运行程序却发送失败未开启mail的debug模式的报错日志是下面这个&#xff1a;org.springframework.mail.MailAuthenticationException: Authentication failed; nested exception is java…

Chainbase :链原生的 Web3 AI 基建设施

“随着 Chainbase 在生态系统和市场方面的进一步拓展&#xff0c;其作为链原生 Web3 AI 基建设施的价值将愈发显著。” 算法、算力和数据是 AI 技术的三大核心要素。实际上&#xff0c;几乎所有的 AI 大模型都在不断革新算法&#xff0c;以确保模型能够跟上行业的发展趋势&…

CUDA Graphs学习与实验

CUDA Graphs学习与实验 一.参考链接二.测试方案三.测试代码 CUDA图&#xff08;CUDA Graphs&#xff09;为CUDA引入了一种全新的工作提交模型。它允许将一系列操作&#xff08;如内核启动&#xff09;以图的形式表示&#xff0c;并通过依赖关系将这些操作连接起来。这种图的定义…