C++多线程:线程的创建、join、detach、joinable方法(二)

1、线程的开始与结束
  • 程序运行起来,生成一个进程,该进程所持有的主线程开始自动运行,main主线程运行完所有的代码从main函数中返回表示整个进程运行完毕,标志着主线程和进程的死亡,等待操作系统回收资源,因为有可能成为孤儿或者僵尸进程所以需要等待。
  • 如果创建自己的线程,也需要从一个函数开始运行(初始函数),一旦运行完毕就代表着这个线程运行结束。
  • 当主线程运行结束,子线程并没有执行完毕也会被操作系统强行终止(因为PCB进程控制块资源的回收),因此如果需要等待子线程正常执行完毕退出需要让主线程等待所有的子线程执行完毕
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread")
2、线程的创建
  • thread库是C++11提供的标准库类,其构造有很多,主要有两种
    • 一种是只提供回调函数的构造
    • 一种是提供回调函数 + 回调函数参数的构造
  • 回调函数:回调函数是创建的线程的执行入口
#include <iostream>
#include <thread>void thread_func1()
{std::cout << "子线程开始执行" << std::endl;std::cout << "子线程执行完毕" << std::endl;
}void thread_func2(int arg)
{std::cout << "子线程开始执行" << std::endl;std::cout <<  arg << std::endl;std::cout << "子线程执行完毕" << std::endl;
}
int main()
{std::thread mythread1(thread_func1);std::thread mythread2(thread_func2, 5);mythread1.join();mythread2.join();std::cout << "main thread executed finish!" << std::endl;return 0;
}
2.1、join方法

join方法的主要用途就是阻塞主线程,等待子线程运行完毕在继续向下执行,类似所有线程都要汇聚到这一点主线程才继续向下执行。

  • 如果不适用join方法进行阻塞等待可能会造成异常
  #include <iostream>#include <thread>void thread_func1(){std::cout << "子线程开始执行1" << std::endl;std::cout << "子线程开始执行2" << std::endl;std::cout << "子线程开始执行3" << std::endl;std::cout << "子线程开始执行4" << std::endl;std::cout << "子线程开始执行5" << std::endl;std::cout << "子线程开始执行6" << std::endl;std::cout << "子线程执行完毕" << std::endl;}int main(){std::thread mythread1(thread_func1);std::cout << "main thread executed finish!" << std::endl;return 0;}

请添加图片描述出现问题的原因是因为主线程已经运行完毕,而mythread1是一个main栈帧里分配的一个临时变量,一旦执行完毕将会释放这个变量的空间导致线程执行错误

  • 如果将mythread1改成new的形式在堆区分配空间,将不会出现错误
  #include <iostream>#include <thread>void thread_func1(){std::cout << "子线程开始执行" << std::endl;std::cout << "子线程执行完毕" << std::endl;}int main(){std::thread *mythread1 = new std::thread(thread_func1);std::cout << "main thread executed finish!" << std::endl;return 0;}

请添加图片描述

  • 不过此时无法看到子线程的输出,因为main线程结束,子线程将失去控制台的读写权限,有点守护线程的味道
    • 一般会让主线程等待子线程运行完毕或者主线程和子线程采用detach进行脱离形成一个新的会话,保证所有的线程安全退出。
2.2、detach方法
  • detach有脱离分离的意思,线程调用该方法可以使得主线程和子线程失去上下级关系,二者平行。子线程运行完毕会自动退出,主线程也无须等待子线程运行完毕。
  • 当主线程和子线程并没有产生交集时,可以使子线程进行脱离,运行完毕自动回收。
    • 举个例子:例如用户登录软件或者Web页面,当账户验证通过时主线程需要继续响应用户的登录请求,而用户的登录日志的保存或者一些其他的日志需要子线程去保存,此时主线程和子线程没有任何交集,总不可能主线程等待子线程存完日志再响应用户吧?因此类似这种情况可以让子线程自动脱离,运行完毕自动结束。
    • 类似于驻留后台的守护线程:脱离的子线程由C++运行时库接管,当子线程运行完毕时由运行时库负责清理该线程的相关资源。
#include <iostream>
#include <thread>void thread_func1()
{std::cout << "子线程开始执行" << std::endl;std::cout << "子线程执行完毕" << std::endl;
}
int main()
{std::thread mythread1(thread_func1);mythread1.detach();std::cout << "main thread executed finish!" << std::endl;return 0;
}

请添加图片描述
一旦调用了detach或者join就不能再调用另外一个方法,否则系统会出现异常

2.3、joinable方法
  • joinable方法主要判断是否可以使用join方法或者detach方法,可以返回true,不可以返回false
  • 一个线程最多只能调用一次join或者detach
#include <iostream>
#include <thread>void thread_func1()
{std::cout << "子线程开始执行" << std::endl;std::cout << "子线程执行完毕" << std::endl;
}int main()
{std::thread mythread1(thread_func1);if(mythread1.joinable()){std::cout << "joinable() == true" << std::endl;mythread1.join();}else{std::cout << "joinable() == false" << std::endl;}std::cout << "------------------------------" << std::endl;if(mythread1.joinable()){std::cout << "joinable() == true" << std::endl;}else{std::cout << "joinable() == false" << std::endl;}std::cout << "main thread executed finish!" << std::endl;return 0;
}

请添加图片描述

3、线程的其他创建方式
3.1、无参自定义类型创建

线程的创建方式也可以通过传入一个类对象,并在类内部对函数运算符()进行重载,使用detach或者join都行。

class MyThreadClass1{
public:MyThreadClass1() {}void operator()() {std::cout << "子线程开始执行" << std::endl;std::cout << "子线程执行完毕" << std::endl;}
};
void test2()
{MyThreadClass1 myThreadClass1;std::thread mythread1(myThreadClass1);mythread1.join();
}
3.2、有参自定义类型创建

有参自定义类型创建时我们需要考虑两种情况:

  • 第一种情况是传入的参数是(指针或引用)、还是普通参数。
  • 第二种情况是使用join还是detach方法
    结论:join方法的话无论传指针或引用、还是普通参数都不会产生任何问题;只有detach对传入的参数有影响。
3.2.1、指针或引用类型
class MyThreadClass2{
public:int &m_i;MyThreadClass2(int &i): m_i(i) {}void operator()() {std::cout << "子线程开始执行" << std::endl;std::cout << "1. m_i = " << m_i << std::endl;std::cout << "2. m_i = " << m_i << std::endl;std::cout << "3. m_i = " << m_i << std::endl;std::cout << "4. m_i = " << m_i << std::endl;std::cout << "5. m_i = " << m_i << std::endl;std::cout << "子线程执行完毕" << std::endl;}
};
int main()
{int my_i = 5;MyThreadClass2 myThreadClass2(my_i);std::thread mythread2(myThreadClass2);mythread2.detach();std::cout << "main thread executed finish!" << std::endl;return 0;
}

请添加图片描述

  • 首先自定义类中需要的是一个引用(引用的本质是指针常量),引用的对象是一个分配在main函数栈帧上的一个普通int类型,再使用detach方法

    • detach方法会使得创建的线程与main主线程进行分离,执行完毕自动回收;而join将会阻塞main主线程
    • 而main主线程执行完毕时会释放掉main栈帧中的空间,因此会把my_i变量释放掉,导致类中引用的对象不存在,所以输出的结果是一个错误的。
  • 解决方法

    • 第一种:将my_i分配到堆区对象,就不是一个main栈帧的本地变量,这样main栈帧退出时将无法回收到这个变量,前提是退出时不进行delete释放。
    • 第二种:将类中的m_i改成普通成员变量不要传入引用或者指针,当一个普通变量当做函数的传入传出参数时是会进行一次拷贝的,而引用将不会拷贝。

这里为什么myThreadClass2对象不会因为main栈帧的释放而报错其实就是因为发生了拷贝构造,下面进行分析

3.2.2、普通变量的拷贝
  • 为了避免引用带来的问题以及对3.2.1中变量地址引用的一个解答,这里使用普通的m_i对象,不接收引用
  • 为了使得输出效果更好看,能够看到执行的流程使用join,不使用detach,但是依然可以使用detach只是输出不全(仅仅是为了输出效果全面)
class MyThreadClass3{
public:int m_i;MyThreadClass3(int i): m_i(i) {std::cout << "构造函数的执行" << std::endl;std::cout << "&m_i变量地址 = " << &m_i << ", &i = " << &i << std::endl;}MyThreadClass3(const MyThreadClass3 &myThreadClass3){this->m_i = myThreadClass3.m_i;std::cout << "拷贝构造函数的执行" << std::endl;}virtual ~MyThreadClass3() {std::cout << "析构函数的执行" << std::endl;}void operator()() {std::cout << "子线程开始执行" << std::endl;std::cout << "1. m_i = " << m_i << std::endl;std::cout << "2. m_i = " << m_i << std::endl;std::cout << "3. m_i = " << m_i << std::endl;std::cout << "4. m_i = " << m_i << std::endl;std::cout << "5. m_i = " << m_i << std::endl;std::cout << "子线程执行完毕" << std::endl;}
};
int main()
{int i = 3;std::cout << "main &i = " << &i << std::endl;MyThreadClass3 myThreadClass3(i);std::thread mythread3(myThreadClass3);mythread3.join();std::cout << "main thread executed finish!" << std::endl;return 0;
}

请添加图片描述

  • 可以很清楚的看到三个变量i和m_i的地址是不一样的,也就是说传入参数的时候普通变量会进行拷贝构造
  • 而对象也会进行拷贝构造,但这里为什么拷贝构造两次就需要深入追源码了,这里不做过多的叙述(初学入门)。但是可以明白的一点就是:myThreadClass3对象传入给线程mythread3变量时会被执行拷贝构造!
3.3、lambda表达式创建
auto mylambdathread = [](){std::cout << "子线程开始执行" << std::endl;std::cout << "子线程执行完毕" << std::endl;
};
std::thread mythread4(mylambdathread);
mythread4.detach();
4、总结

到此学习完了C++最基本的线程的创建与使用

  • join方法会阻塞执行该代码的线程(main中执行这行代码就会阻塞main),detach方法不会。detach方法不会的主要原因是脱离了当前进程所在的会话session,成为一个独立的进程(该进程只有当前线程),执行完毕会被操作系统自动回收资源
  • 对于join和detach方法的使用需要根据实际情况判定使用哪个
  • 对于数据类型指针或引用、还是普通变量传入时是否会进行拷贝,注意会不会因为搭配detach而产生错误。

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

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

相关文章

论文笔记:Retrieval-Augmented Generation forAI-Generated Content: A Survey

北大202402的RAG综述 1 intro 1.1 AICG 近年来&#xff0c;人们对人工智能生成内容&#xff08;AIGC&#xff09;的兴趣激增。各种内容生成工具已经精心设计&#xff0c;用于生产各种模态下的多样化对象 文本&代码&#xff1a;大型语言模型&#xff08;LLM&#xff09;…

Linux网络配置(超详细)

Linux网络配置大全 Linux网络配置一.网络地址配置网络地址查看–ifconfig使用网络配置命令设置网络接口参数-ifconfig禁用(临时)或者重新激活网卡设置虚拟网络接口 修改网络配置文件网络接口配置文件 IP命令详解OPTIONS选项OBJECT对象 ip link 二、获取和修改主机名hostname查看…

E5063A是德科技E5063A网络分析仪

181/2461/8938产品概述&#xff1a; Keysight E5063A 是一款低成本网络分析仪&#xff0c;可为测试天线、电缆、滤波器和 PCB 等简单无源元件提供优化的性能和功能。Keysight E5063A 为您的企业提供价格和性能之间的最佳平衡&#xff0c;以满足您的业务和技术要求。它利用行业…

QT_day4:对话框

1、完善对话框&#xff0c;点击登录对话框&#xff0c;如果账号和密码匹配&#xff0c;则弹出信息对话框&#xff0c;给出提示”登录成功“&#xff0c;提供一个Ok按钮&#xff0c;用户点击Ok后&#xff0c;关闭登录界面&#xff0c;跳转到其他界面 如果账号和密码不匹配&…

Trapcode Particular---打造惊艳粒子效果

Trapcode Particular是Adobe After Effects中的一款强大3D粒子系统插件&#xff0c;其能够创造出丰富多样的自然特效&#xff0c;如烟雾、火焰和闪光&#xff0c;以及有机的和高科技风格的图形效果。Trapcode Particular功能丰富且特色鲜明&#xff0c;是一款为Adobe After Eff…

Elasticsearch:语义搜索即服务处于卓越搜索的中心

作者&#xff1a;来自 Elastic Sherry Ger, Stephen Brown 对于许多企业来说&#xff0c;搜索卓越中心&#xff08;center of excellence - COE&#xff09;向其用户提供搜索服务&#xff0c;从不同的数据源中整理知识&#xff0c;并将搜索功能集成到其内部和外部应用程序中。…

第4章.精通标准提示,引领ChatGPT精准输出

标准提示 标准提示&#xff0c;是引导ChatGPT输出的一个简单方法&#xff0c;它提供了一个具体的任务让模型完成。 如果你要生成一篇新闻摘要。你只要发送指示词&#xff1a;汇总这篇新闻 : …… 提示公式&#xff1a;生成[任务] 生成新闻文章的摘要&#xff1a; 任务&#x…

完整部署一套k8s-v.1.28.0版本的集群

一、系统情况 虚拟机版本&#xff1a;esxi 6.7 系统版本&#xff1a;centos7.9_2009_x86 配置&#xff1a;4核8G&#xff08;官网最低要求2核2G&#xff09; 192.168.0.137 master节点 192.168.0.139 node2节点 192.168.0.138 node1节点&#xff08;节点扩容练习&#xf…

(五)ROS2学习--创建调用其它包接口的一个发布者

这里写自定义目录标题 一、背景二、构建步骤1. 构建项目包2. 创建消息接口3. 修改“package.xml”4. 修改"src/smart_car/CMakeLists.txt"5. 创建发布者程序 三、编译及验证1. 编译2. 验证 一、背景 主机&#xff1a;Ubuntu20.04 介绍&#xff1a;基于上一篇&#x…

大话设计模式之工厂模式

工厂模式&#xff08;Factory Pattern&#xff09;是一种创建型设计模式&#xff0c;它提供了一种创建对象的最佳方式&#xff0c;而无需指定将要创建的对象的确切类。通过使用工厂模式&#xff0c;我们可以将对象的创建和使用分离&#xff0c;从而使代码更具灵活性和可维护性。…

音视频处理 - 音频概念详解,码率,采样率,位深度,声道,编码

1. 音频采样 与视频不同&#xff0c;音频的最小单位不是一帧&#xff0c;而是一个采样。 采样是当前一刻声音的声音样本&#xff0c;样本需要经过数字转换才能存储为样本数据。 真实声音是连续的&#xff0c;但是在计算机中&#xff0c;声音是离散且均匀的声音样本。 2. 位深…

22 多态

目录 多态的概念多态的定义及实现抽象类多态的原理单继承和多继承关系中的虚函数表继承和多态常见的面试问题 前言 需要声明的&#xff0c;下面的代码和解释的哦朴实vs2013x86环境&#xff0c;涉及指针是4bytes&#xff0c;如果要其他平台下&#xff0c;部分代码需要改动。比…

ttkbootstrap界面美化系列之Notebook(四)

在简单的界面设计中&#xff0c;Notebook也是常用的组件之一&#xff0c;Notebook组件的引入可以根据标签来切换不同的界面。使得界面更有层次感&#xff0c;不必都挤在一个界面上。在tkinter中就有Notebook组件&#xff0c;在ttkbootstrap中&#xff0c;同样也对Notebook进行了…

四、Yocto创建静态IP和VLAN(基于raspiberrypi 4B)

Yocto创建VLAN配置 在车载域控中很多时候需要创建VLAN&#xff0c;本小节记录如何为yocto构建出来的image自动化创建静态IP以及VLAN。 关于各种VLAN的配置参考&#xff1a;VLAN 1. ubuntu系统中使用netplan创建VLAN 正常情况下我们在ubuntu系统中可以通过netplan来自动化创建…

会话跟踪技术(Session 以及Cookie)

一: 前提概要 1>会话: 会话指的是用户打开浏览器, 访问某些web服务器资源的时候, 会话就会进行建立, 直到有一方断开, 那么会话才会结束, 需要注意的一点是, 一次的会话可以有多次的请求以及响应 2>会话跟踪: 是一种用于维护浏览器状态的方法, 服务器需要识别多次的请求,…

CTF题型 php://filter特殊编码绕过小汇总

CTF题型 php://filter特殊编码绕过小汇总 文章目录 CTF题型 php://filter特殊编码绕过小汇总特殊编码base64编码string过滤器iconv字符集 例题1.[Newstarctf 2023 week2 include]2.[Ctfshow web 117] php://filter 是一个伪协议&#xff0c;它允许你读取经过过滤器处理的数据流…

Jmeter 从登录接口提取cookie 并 跨线程组调用cookie (超详细)

文章目录 一、开始前的准备二、 业务场景介绍三、从登录接口提取cookies四、跨线程组调用cookies 一、开始前的准备 1、安装Jmeter&#xff0c;参考文章&#xff1a;JMeter 3.1 和JMeterPlugin的下载安装 2、设置配置文件使Cookie管理器保存cookie信息。 修改apache-jmeter-x…

C语言:编译和链接

前言 在ANSI C的任何一种实现中&#xff0c;存在两个不同的环境。 第1种是翻译环境&#xff0c;在这个环境中源代码被转换为可执行的机器指令&#xff08;二进制指令&#xff09;。第2种是执行环境&#xff0c;它用于实际执行代码。 目录 1.翻译环境1.1 预处理&#xff08;预编…

iOS - Runtime - Class-方法缓存(cache_t)

文章目录 iOS - Runtime - Class-方法缓存(cache_t)1. 散列表的存取值 iOS - Runtime - Class-方法缓存(cache_t) Class内部结构中有个方法缓存&#xff08;cache_t&#xff09;&#xff0c;用散列表&#xff08;哈希表&#xff09;来缓存曾经调用过的方法&#xff0c;可以提高…

vlan间单臂路由

【项目实践4】 --vlan间单臂路由 一、实验背景 实验的目的是在一个有限的网络环境中实现VLAN间的通信。网络环境包括两个交换机和一个路由器&#xff0c;交换机之间通过Trunk链路相连&#xff0c;路由器则连接到这两个交换机的Trunk端口上。 二、案例分析 在网络工程中&#…