C++11多线程:线程的创建及启动

news2024/11/24 11:47:27

文章目录

  • 启动线程
    • 传递函数对象为参数
    • 传递成员函数为参数
    • 传递全局函数为参数
    • 传递lambda函数为参数
    • 也可调用std::thread的无参构造
  • join()、joinable()、detach() 等函数
    • Join函数
    • detach 函数
    • joinable函数
  • Join函数到底干了什么?
  • 必须join或者detach吗?
  • 线程资源不能被覆盖

在C++11之前的C++98/03标准是不支持的多线程的。想要使用多线程需要使用使用POSIX C 或者其他API功能。
在C++11标准中引入的支持多线程。想要使用C++11中的多线程,需要 添加 #include <thread>
在C++11标准库中对多线程支持以及用于管理线程的函数和类在 <thread>中声明,而那些用于保护共享数据的函数和类在其他头文件中声明。
每个线程都必须具有一个初始函数(initial function),新线程的执行在这里开始。
初始线程始于 main(),而新线程始于与创建的thread对象拥有的初始函数。

启动线程

线程是通过构造 std::thread对象来开始的,该对象指定了线程上要运行的任务。在最简单的情况下,该任务仅仅是一个普普通通的返回 void 且不接受参数的函数。这个函数在自己的线程上运行,直到返回,然后线程停止。该任务也可以是一个 函数对象lambda表达式成员函数

传递函数对象为参数

#include <iostream>
#include <thread>
using namespace std;
class background_task_1
{
public:

	void operator()()const //无参 重载了operator()函数的类的实例称为函数对象
	{
		std::cout << "background_task_1::operator() ...." << endl;
		
	}
	
	void operator()(int a)const//有参 重载了operator()函数的类的实例称为函数对象
	{
		std::cout << "background_task_1::operator() .... a =" << a << endl;
	}
};
//
int main(int argc, char *argv[])
{
	//thread t1(background_task_1());//编译报错
	background_task_1 task1; //创建一个函数对象
	thread t1(task1);//会启动一个新线程执行函数对象task1里无参的operator()函数
	thread t2(task1,2);//会启动一个新线程执行函数对象task1里有参的operator()(int)函数
	t1.join();//等待t1线程(被调用线程)执行结束完,(调用线程)这里是主线程然后才会再结束。
	t2.join();//等待t1线程(被调用线程)执行结束完,(调用线程)这里是主线程然后才会再结束。
	//总之只要每个新启动的线程都调用join()方法,那么调用线程都会等待所有的线程执行完毕后,才会结束主线程。
}

打印结果:
在这里插入图片描述
注意:thread t1(background_task_1()); 如这样传递一个临时的且未命名的变量,编译器会解释为如下:
声明了函数 my_thread,它接受单个参数(参数类型是指向不接受参数同时返回background_task_1对象的函数的指针),
并返回thread 对象。而不是启动一个新线程。
编译器无法分清该行是要调用构造函数还是定义变量,从而导致出现 parentheses were disambiguated as a function declaration 警
告。
在这里插入图片描述
总之 在传递匿名对象做为实参时,需注意"C++最棘手的解析"。
可以使用以下两种方式
1. 使用强制类型转换
thread t1((background_task_1()));
方式1中额外的括号避免其解释为函数声明,从而让 t1被声明为std::thread类型的变量。
2.使用{}调用构造函数 C++1
thread t1{(background_task_1()};
方式2中使用了新的统一初始化语法,用大括号而不是括号,同样也是声明一个变量。

传递成员函数为参数

class background_task_2
{
public:
	void member_func()
	{
		std::cout << "background_task_2::member_func .... " << endl;
		
	}

	void member_func1()
	{
		std::cout << "background_task_2::member_func1 ...."<< endl;
		
	}

	void member_func1(int b)
	{
		std::cout << "background_task_2::member_func1 .... b = " << b << endl;
	}
};

int main(int argc, char *argv[])
{
	background_task_2 task2;
	thread t1(&background_task_2::member_func,&task2);//member_func 为当前类中唯一此名的函数,不存在重载可以这样传递。
	t1.join();
	return 0;
}

int main(int argc, char *argv[])
{
	background_task_2 task2;
	//member_func1函数存在重载的情况下
	void (background_task_2::*fun1)() = &background_task_2::member_func1;
	void (background_task_2::*fun2)(int) = &background_task_2::member_func1;
	
	thread t2(fun1,&task2);//启动一个新线程,执行member_func1无参函数
	thread t3(fun2,&task2,80);//启动一个新线程,执行member_func1有参函数
	
	t2.join();
	t3.join();
	
	return 0;
}

在这里插入图片描述
在这里插入图片描述

传递全局函数为参数

void global_func(int a)
{
	std::cout << "global_func.... a = " << a << endl;
}

int main(int argc, char *argv[])
{
	thread t1(global_func,30);//函数名,是第一个参数,函数中的参数,依次往后写.
	t1.join();
	return 0;
}

在这里插入图片描述

传递lambda函数为参数

int main(int argc, char *argv[])
{
	//thread t1(global_func,30);
	int a = 10;
	int b = 20;
	thread t1([=]{
		cout << "a + b = " << a+b << endl;
	});
	t1.join();
	return 0;
}

在这里插入图片描述

也可调用std::thread的无参构造

int main(int argc, char *argv[])
{
	thread t1;//单独使用它,没有意思,因没有绑定线程函数。一般用于接收其他线程对象。
	t1.join();//当t1是根据无参构造生成的对象,直接调用join会报错,后面讲joinable时用的
}

join()、joinable()、detach() 等函数

因为一旦开始线程,你需要显示地决定是要等待它完成(通过使用join等待它),还是让它自行运行(通过detach分离它)。
如果在std::thread对象销毁前未做决定,那么你的程序会被终止(thread对象的析构函数调用std::terminate())。
C++11的多线程的执行和Java的多线程的执行不太一样。Java中的多线程不用指定结合它 还是 分离它。

Join函数

Join(等待):
当主线程调用了 join 方法后,它会阻塞直到子线程完成并返回控制权。
主线程在执行 join 后,通常会继续执行后续代码,因为它已经知道子线程的状态。
如果子线程抛出异常,那么主线程会在异常被处理之前接管控制权。
使用 join 时,如果子线程没有完成,主线程将一直处于阻塞状态直到子线程结束。

int main(int argc, char *argv[])
{
	//thread t1(global_func,30);
	int a = 10;
	int b = 20;
	thread t1([=]{
		cout << "a + b = " << a+b << endl;
	});
	t1.join();//主线程会阻塞到这里,等待子线程执行完毕后,再执行下面一句
	cout << "main..." << endl;
	return 0;
}

另外一个例子:
假设有两个线程,线程A和线程B。线程A被托管在thread对象A中。在线程B中执行对象A的join()函数,那么线程B就会被阻塞住,直到线程A执行完成后,线程B才会执行A.join()后面的代码。

//使当前线程睡眠的封装函数
void Sleep(long ms){
	std::this_thread::sleep_for(std::chrono::milliseconds(ms));
}

void f(){
    Sleep(1000);//会休眠1秒
    cout<<"I am f thread"<<endl;
}

int main()
{
    thread t1(f);//线程A被托管在thread对象t1中
    t1.join();//线程B 指的就是 mian函数主线程
    cout<<"run main thread"<<endl;
}

在这里插入图片描述
但是如果创建一个空的thread对象

int main()
{
    thread t1;
    t1.join();
    cout<<"run main thread"<<endl;
}

在这里插入图片描述
空的thread对象,其实是没有持有线程资源,这种情况是不能join的。

detach 函数

Detach(分离):
detach 操作使得子线程不再需要与主线程同步,它可以独立地执行自己的任务。
子线程一旦进入终止状态,无论是否使用了 detach,都会自动从系统中移除。
使用 detach 的线程通常不会被主线程回收资源或杀死,除非有其他线程尝试进行这些操作。
detach 通常用于多线程编程中,减少主线程因为等待子线程而停滞的时间。
总结来说,join 是用来等待并接收子线程的控制权的,而 detach 是用来使子线程成为独立的进程,不再需要主线程的管理。这两种方法适用于不同的情况,选择哪一种取决于你的需求。

int main(int argc, char *argv[])
{
	//thread t1(global_func,30);
	int a = 10;
	int b = 20;
	thread t1([=]{
		cout << "a + b = " << a+b << endl;
	});
	t1.detach();//无须等待子线程的执行完毕,主线程会立即执行下一句
	cout << "main..." << endl;
	return 0;
}

在这里插入图片描述

joinable函数

Joinable(可结合)
joinable()函数的功能,官方文档说的非常晦涩难懂。我个人比较倾向于这种解释:" 判断当前的线程对象是否是可执行线程对象。"
一般有三种情况,joinable()返回false:
(1)该thread对象通过无参构造函数构造;
(2)该thread对象执行过join()或者detach();
(3)该thread对象被move过,但是move的接受方的joinable()结果为true。

void f5(){

};

void f5(){

};

int main()
{
    thread t1;
    thread t2(f5);
	cout<<"t2 : "<<t2.joinable()<<endl;
    t1=move(t2);//调移动构造函数
    cout<<"t1 : "<<t1.joinable()<<endl;
    cout<<"t2 : "<<t2.joinable()<<endl;
    t1.join();
};

在这里插入图片描述
注意:t2在这里把线程资源权移交给了t1,所以t2在被析构时,其joinabe() 返回false。所以不用使用t2.join() 或者 t2.detach();时也可以正常被析构。不会报 terminate called without an active exception。

Join函数到底干了什么?

一个非常容易忽略的点:join函数实际上是放弃了持有的线程资源。对于没有持有线程资源的thread对象不能join,这是为了保证thread对象只能join一次。joinable函数实际上是检测thread对象是否持有线程资源。
假如说我们创建了一个空的thread对象,没有传入函数,自然不会持有操作系统层面的线程资源。强行join就会报错。
假如我们给它move进来一个资源以后,那么它就可以join了。
这个对象在执行了join函数以后,彻底放弃了对于线程资源的管理权,joinable状态变成了false。
同样的道理,thread被move以后,放弃了线程资源,joinable状态变成了false。
一句话:joinable()返回值为true的对象,也就是持有线程资源的对象,才能join。

必须join或者detach吗?

从宏观上看,是的。
一句话描述:C++程序中,在thread对象执行析构函数的时候,不允许持有线程资源。执行析构之前,要么通过join放弃资源的控制权,要么将线程资源move出去。

std::thread类的析构源码

~thread() _NOEXCEPT
{	// clean up
	if (joinable())
		_XSTD terminate();
}

即 当系统在调当前线程对象的析构函数时,若joinable()返回true(表明当前线程即没有join或没有detach 或没有移交线程资源),则会调terminate() 终止程序。

线程资源不能被覆盖

void f6(){
}
int main()
{
    thread t1(f6);
    thread t2(f6);
    t1=move(t2);   //覆盖t1持有的线程资源
    cout<<"t1 : "<<t1.joinable()<<endl;
    cout<<"t2 : "<<t2.joinable()<<endl;
    t1.join();
}

在这里插入图片描述
可以看到当t1持有线程资源时,是不能被t2的线程资源覆盖的。

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

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

相关文章

AI少女/HS2甜心选择2 仿碧蓝航线人物卡全合集打包

AI少女/HS2甜心选择2 仿碧蓝航线人物卡全合集打包 内含&#xff1a;埃吉尔 女仆装花园 新泽西 白雪之仪库尔斯克信浓泳装埃吉尔风纪委员大凤路易九世旗袍能代夜响的绝园英仙座 护士 下载地址&#xff1a; https://www.changyouzuhao.cn/13366.html

springboot153相亲网站

简介 【毕设源码推荐 javaweb 项目】基于springbootvue 的 适用于计算机类毕业设计&#xff0c;课程设计参考与学习用途。仅供学习参考&#xff0c; 不得用于商业或者非法用途&#xff0c;否则&#xff0c;一切后果请用户自负。 看运行截图看 第五章 第四章 获取资料方式 **项…

ONLYOFFICE 8.0 测评:重塑办公新标杆,你绝对不能错过的版本!

ONLYOFFICE 8.0 测评&#xff1a;办公新境界的全新突破 一、全新的界面设计二、可填写的 PDF 表单 免费表单模板三、双向文本四、电子表格中的新增功能五、协作功能升级六、跨平台性能优化七、强化安全性八、更丰富的插件生态九、辅助功能&#xff1a;优化的屏幕朗读器 随着科…

程序员阿宝过年-UMLChina建模知识竞赛第5赛季第5轮

DDD领域驱动设计批评文集 做强化自测题获得“软件方法建模师”称号 《软件方法》各章合集 参考潘加宇在《软件方法》和UMLChina公众号文章中发表的内容作答。在本文下留言回答。 只要最先答对前3题&#xff0c;即可获得本轮优胜。 如果有第4题&#xff0c;第4题为附加题&am…

Ubuntu+GPU搭建Stable-Diffusion教程

【前序】已经安装anaconda 1.git拉取项目到本地 执行git clone https://github.com/AUTOMATIC1111/stable-diffusion-webui.git 进入项目目录下 cd stable-diffusion-webui/ 2. 安装对应Python依赖包 首先安装pytorch和torchvision&#xff0c;若是GPU环境的用户需要安装与cu…

[基础IO]动静态库{创建/使用/加载}

文章目录 1.创建静态库2.使用静态库3.动态库 1.创建静态库 预备知识: gcc main.c mymath.c myprint.c -o my.exe / gcc main.o mymath.o myprint.o -o my.exe三个.c/.o文件可以编译成为可执行程序 编写源文件与头文件[库的源文件没有main函数]将所有的源文件编译生成.o文件。(…

电工需要掌握的电路

1、通用型变频器接线示意图 2、三线式运转控制台达正反转电路图 3、单相220V接线实物图、三相380V接线实物图 4、变频器操作面板功能介绍 5、风暖浴霸实物接线电路图 6、车辆出入门闸检测实物接线电路图 7、电位器实现两台变频器同步调频实物接线图 8、门禁系统是如何工作的实物…

解决zabbix图像中文乱码

使用zabbix查看监控图像信息&#xff0c;发现会有中文乱码现象。 解决方法如下&#xff1a; 1.拷贝windows文字文件到服务器上 C:\Windows\Fonts目录下拷贝自己需要的中文语言文件 2.修改配置文件 vim /usr/share/zabbix/include/defines.inc.php 81行 define(ZBX_GRAPH_F…

vue3接入微信扫码授权登录流程

一、概要 本篇主要介绍两点,相关文档请查阅微信开平台 准备工作 | 微信开放文档 微信开放平台的登录授权相关流程vue3如何接入微信开放平台的微信授权登录功能二、微信开放平台的登录授权相关流程 第三方发起微信授权登录请求,微信用户允许授权第三方应用后,微信会拉起应…

6个.NET开源且强大的快速开发框架(帮助你提高生产效率)

中台Admin&#xff08;Admin.Core&#xff09; 中台Admin&#xff08;Admin.Core&#xff09;是前后端分离权限管理系统&#xff0c;前端 UI 基于Vue3开发&#xff0c;后端 Api 基于.NET 8.0开发。支持多租户、接口权限、数据权限、动态 Api、任务调度、OSS 文件上传、滑块拼图…

格式化日期注解@JsonFormat的使用和TimeZone时区问题

JsonFormat的使用 目的 为了便于date类型字段的序列化和反序列化&#xff0c;需要在数据结构的Date、Timestamp、DateTime类型的字段上用JsonFormat注解进行注解 使用 JsonFormat注解是一个时间格式化注解&#xff0c;比如我们存储在mysql中的数据是date类型的&#xff0c;当…

qml为程序设置图标(100%成功!3步搞定!)

步骤1 打开 CMakeLists.txt 文件&#xff0c;在 main.cpp 这行代码下面&#xff0c;添加一句&#xff1a;icon.rc。 步骤2 此时 icon.rc 这个文件还不存在&#xff0c;我们需要在目录中新建一个 .txt 文件&#xff0c;然后把文件名和后缀名改为 icon.rc。 步骤3 在 Qt Creat…

华为通过流策略实现策略路由(重定向到不同的下一跳)

通过流策略实现策略路由&#xff08;重定向到不同的下一跳&#xff09; 组网图形 图1 配置策略路由组网图 策略路由简介配置注意事项组网需求配置思路操作步骤配置文件 策略路由简介 传统的路由转发原理是首先根据报文的目的地址查找路由表&#xff0c;然后进行报文转发。但…

父类之王“Object”类和内部类

&#x1f468;‍&#x1f4bb;作者简介&#xff1a;&#x1f468;&#x1f3fb;‍&#x1f393;告别&#xff0c;今天 &#x1f4d4;高质量专栏 &#xff1a;☕java趣味之旅 欢迎&#x1f64f;点赞&#x1f5e3;️评论&#x1f4e5;收藏&#x1f493;关注 &#x1f496;衷心的希…

DS:时间复杂度和空间复杂度

创作不易&#xff0c;感谢三连&#xff01; 一、算法 1.1 什么是算法 算法(Algorithm):就是定义良好的计算过程&#xff0c;他取一个或一组的值为输入&#xff0c;并产生出一个或一组值作为输出。简单来说算法就是一系列的计算步骤&#xff0c;用来将输入数据转化成输出结果。…

MongoDB复制集实战及原理分析

文章目录 MongoDB复制集复制集架构三节点复制集模式PSS模式&#xff08;官方推荐模式&#xff09;PSA模式 典型三节点复制集环境搭建复制集注意事项环境准备配置复制集复制集状态查询使用mtools创建复制集安全认证复制集连接方式 复制集成员角色属性一&#xff1a;Priority 0属…

【边缘服务】

目录 边缘服务是一种新兴的技术趋势边缘服务的应用领域非常广泛边缘服务的核心特点之一是分布式部署另一个核心特点是低延迟边缘服务还可以提供更好的安全性和隐私保护总的来说 边缘服务是一种新兴的技术趋势 它将计算、存储和网络资源推向网络边缘&#xff0c;以实现更低的延…

【大厂AI课学习笔记】1.4 算法的进步(4)关于李飞飞团队的ImageNet

第一个图像数据库是ImageNet&#xff0c;由斯坦福大学的计算机科学家李飞飞推出。ImageNet是一个大型的可视化数据库&#xff0c;旨在推动计算机视觉领域的研究。这个数据库包含了数以百万计的手工标记的图像&#xff0c;涵盖了数千个不同的类别。 基于ImageNet数据库&#xf…

STM32--SPI通信协议(2)W25Q64简介

一、W25Q64简介 1、W25Qxx中的xx是不同的数字&#xff0c;表示了这个芯片不同的存储容量&#xff1b; 2、存储器分为易失性与非易失性&#xff0c;主要区别是存储的数据是否是掉电不丢失&#xff1a; 易失性存储器&#xff1a;SRAM、DRAM&#xff1b; 非易失性存储器&#xff…

RabbitMQ_00000

MQ的相关概念 RabbitMQ官网地址&#xff1a;https://www.rabbitmq.com RabbitMQ API地址&#xff1a;https://rabbitmq.github.io/rabbitmq-java-client/api/current/ 什么是MQ&#xff1f; MQ(message queue)本质是个队列&#xff0c;FIFO先入先出&#xff0c;只不过队列中…