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

文章目录

  • 启动线程
    • 传递函数对象为参数
    • 传递成员函数为参数
    • 传递全局函数为参数
    • 传递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 upif (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.hqwc.cn/news/451271.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C++ OpenGL绘制三维立体skybox场景obj模型AABB碰撞检测旋转动画界面

程序示例精选 C OpenGL绘制三维立体skybox场景obj模型AABB碰撞检测旋转动画界面 如需安装运行环境或远程调试&#xff0c;见文章底部个人QQ名片&#xff0c;由专业技术人员远程协助&#xff01; 前言 这篇博客针对《C OpenGL绘制三维立体skybox场景obj模型AABB碰撞检测旋转动…

产品开发工程师岗位的基本职责概述(合集)

产品开发工程师岗位的基本职责概述1 职责&#xff1a; 1、新产品开发和老产品升级改进; 2、国内外新品的收集和介绍以及应用建议&#xff0c;与市场部交流化妆品新技术、新进展、及新产品推荐建议; 3、产品开发过程的项目流程跟进及技术验证&#xff0c;技术中心内跨部门合作项…

【Linux网络编程一】网络基础1(网络框架)

【Linux网络编程一】网络基础1&#xff08;网络框架&#xff09; 一.什么是协议1.通信问题2.协议本质3.网络协议标准 二.协议分层1.为什么协议要分层2.如何具体的分层 三.操作系统OS与网络协议栈的关系1.核心点&#xff1a;网络通信贯穿协议栈 四.局域网中通信的基本原理1.封装…

机器学习 低代码 ML:PyCaret 的使用

✅作者简介&#xff1a;人工智能专业本科在读&#xff0c;喜欢计算机与编程&#xff0c;写博客记录自己的学习历程。 &#x1f34e;个人主页&#xff1a;小嗷犬的个人主页 &#x1f34a;个人网站&#xff1a;小嗷犬的技术小站 &#x1f96d;个人信条&#xff1a;为天地立心&…

png图片怎么转换成jpg?四个方法搞定不求人

在数字图像处理领域&#xff0c;PNG和JPG是两种常见的图片格式。PNG以无损压缩而闻名&#xff0c;适用于保存透明背景和保留图像细节&#xff1b;而JPG以有损压缩而著称&#xff0c;适用于在较小的文件大小下保持照片质量。有时候&#xff0c;您可能需要将PNG格式的图片转换为J…

<设计模式>单例模式懒汉和饿汉

目录 一、单例模式概述 二、懒汉模式和饿汉模式 1.饿汉模式 1.1代码实现 1.2实现细节 1.3模式优劣 2.懒汉模式 2.1代码实现 2.2实现细节 2.3模式优劣 三、多线程下的线程安全问题 1.懒汉和饿汉线程安全问题分析 1.1安全的饿汉模式 1.2不安全的懒汉模式 2.懒汉线程…

用Python画一条祥龙,祝您新年龙腾万里

用Python画一条祥龙&#xff0c;祝您新年龙腾万里 龙年到了&#xff0c;祝大家新年龙行龘龘&#xff0c;龙腾万里&#xff01; 从2021年开始&#xff0c;我每年都用Python画一幅当年生肖的图。 用Python标准库turtle画一头金牛&#xff0c;祝您新年牛气冲天&#xff01; 用P…

SpringBoot项目打包成jar包供第三方使用实践

文章目录 1.使用者手动配置 basePackages1.1 第三方jar项目1.2 使用者项目1.2.1 使用者配置1.2.2 项目测试 2.使用者通过注解的方式引入2.1 第三方jar项目2.2 使用者项目2.2.1 使用者配置2.2.2 项目测试 3.SpringBoot Starter 方式3.1 第三方jar项目3.2 使用者项目3.2.1 使用者…

Unity3d C# 在WebGL平台加载并解析xml文件实现总结

前言 xml是可扩展标记语言&#xff0c;由一系列的元素、属性、值节点等构成的一个树形结构&#xff0c;除了可读性差一点&#xff0c;别的用于存储一些结构化的数据还是比较方便的。这个功能在Unity3d端的实现是比较方便快捷的&#xff1a; void GetXML1() {string filePath …

【Docker进阶】镜像制作-用Dockerfile制作镜像(一)

进阶一 docker镜像制作 文章目录 进阶一 docker镜像制作用dockerfile制作镜像dockerfile是什么dockerfile格式为什么需要dockerfileDockerfile指令集合FROMMAINTAINERLABELCOPYENVWORKDIR 用dockerfile制作镜像 用快照制作镜像的缺陷&#xff1a; 黑盒不可重复臃肿 docker…

Python 轻量级定时任务调度:APScheduler

简述 APscheduler (Advanced Python Scheduler)&#xff0c;作用为按指定的时间规则执行指定的作业。提供了基于日期date、固定时间间隔interval 、以及类似于Linux上的定时任务crontab类型的定时任务。该框架不仅可以添加、删除定时任务&#xff0c;还可以将任务存储到数据库…

ES6中新增Array.of()函数的用法详解

new Array()方法 ES6为Array增加了of函数用一种明确的含义将一个或多个值转换成数组。因为用new Array()构造数组的时候&#xff0c;是有二意性的。 构造时&#xff0c;传一个参数&#xff0c;实际上是指定数组的长度&#xff0c;表示生成多大的数组。 构造时&#xff0c;传…