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

news2025/4/19 7:58:51
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.coloradmin.cn/o/1555318.html

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

相关文章

五、基于KubeAdm搭建多节点K8S集群

如需查阅上一步骤,请点击下面链接:四、戴尔R630本地服务器Linux Centos7.9系统安装docker-ce-20.10.10-3.el7版本-CSDN博客文章浏览阅读727次,点赞12次,收藏13次。1、准备工作3、Linux Centos7.9系统的iDRAC远程管理、网络设置、SecureCRT远程登录终端、企业级静态ip地址配…

SpringBoot实现RabbitMQ的定向交换机(SpringAMQP 实现Direct定向交换机)

文章目录 Direct 交换机特点实战声明交换及其队列(以注解方式)发消息 应用 上一篇文章中的 Fanout 模式&#xff0c;一条消息&#xff0c;会被所有订阅其交换机的队列都消费。 但是&#xff0c;在某些场景下&#xff0c;我们希望不同的消息被不同的队列消费。这时就要用到 Dir…

Unity-通过AB包使用SpriteAtlas图集(基于unity2018)

项目遇到了一个性能问题&#xff0c;需要优化UI。其中就涉及UI的合批问题&#xff0c;其中自然而然就会关联到图集的概念。旧版图集&#xff0c;Legacy Atlas&#xff0c;还没有太研究。今天主要看一下SpriteAtlas怎么使用的。 因为我们项目资源工程和Runtime是分离的&#xf…

(十二)图像的Sobel梯度锐化

环境&#xff1a;Windows10专业版 IDEA2021.2.3 jdk11.0.1 OpenCV-460.jar 系列文章&#xff1a; &#xff08;一&#xff09;PythonGDAL实现BSQ&#xff0c;BIP&#xff0c;BIL格式的相互转换 &#xff08;二&#xff09;BSQ,BIL,BIP存储格式的相互转换算法 &#xff08;三…

RabbitMQ 延时消息实现

1. 实现方式 1. 设置队列过期时间&#xff1a;延迟队列消息过期 死信队列&#xff0c;所有消息过期时间一致 2. 设置消息的过期时间&#xff1a;此种方式下有缺陷&#xff0c;MQ只会判断队列第一条消息是否过期&#xff0c;会导致消息的阻塞需要额外安装 rabbitmq_delayed_me…

327京东一面

1.项目相关 2.手撕SQL 两道 3.JMeter性能测试 首先&#xff0c;进行基准测试&#xff1a; 单用户测试&#xff08;单用户循环多次得到的数据&#xff09;&#xff1b;为多用户并发执行提供参考 其次&#xff0c;进行负载测试&#xff1a; 通过逐步增加系统负载&#xff0…

2024年水电站大坝安全监测工作提升要点

根据《水电站大坝运行安全监督管理规定》&#xff08;国家发改委令第23号&#xff09;和《水电站大坝运行安全信息报送办法》&#xff08;国能安全〔2016〕261号&#xff09;的相关规定、要求&#xff0c;电力企业应当在汛期向我中心报送每日大坝汛情。近期&#xff0c;全国各地…

Qt+OpenGL入门教程(三)——绘制三角形

通过前两篇文章的学习&#xff0c;我想大家应该有了基本的理解&#xff0c;我们接下来实操一下。 创建Qt OpenGL窗口 QOpenGLWidget QGLWidget是传统QtOpenGL模块的一部分&#xff0c;与其他QGL类一样&#xff0c;应该在新的应用程序中避免使用。相反&#xff0c;从Qt5.4开始…

蓝桥杯23年第十四届省赛真题-三国游戏|贪心,sort函数排序

题目链接&#xff1a; 1.三国游戏 - 蓝桥云课 (lanqiao.cn) 蓝桥杯2023年第十四届省赛真题-三国游戏 - C语言网 (dotcpp.com) 虽然这道题不难&#xff0c;很容易想到&#xff0c;但是这个视频的思路理得很清楚&#xff1a; [蓝桥杯]真题讲解&#xff1a;三国游戏&#xff0…

备考ICA----Istio实验14---出向流量管控Egress Gateways实验

备考ICA----Istio实验14—出向流量管控Egress Gateways实验 1. 发布测试用 pod kubectl apply -f istio/samples/sleep/sleep.yaml kubectl get pods -l appsleep2. ServiceEntry 创建一个ServiceEntry允许流量访问edition.cnn.com egressgw/edition-ServiceEntry.yaml api…

前端bugs

问题&#xff1a; Failed to load plugin typescript-eslint declared in package.json eslint-config-react-app#overrides[0]: Cannot find module eslint/package.json 解决&#xff1a; google了一晚上还得是chatgpt管用 运行以下命令【同时还要注意项目本身使用的Node版…

亚信安全联合人保财险推出数字安全保障险方案,双重保障企业数字化转型

数字化发展&#xff0c;新兴技术的应用与落地带来网络攻击的进一步演进升级&#xff0c;同时全球产业链供应链融合协同的不断加深&#xff0c;更让网络威胁的影响范围与危害程度不断加剧。 企业单纯依靠自身安全能力建设&#xff0c;能否跟上网络威胁的进化速度&#xff1f;能否…

Day55:WEB攻防-XSS跨站CSP策略HttpOnly属性Filter过滤器标签闭合事件触发

目录 XSS跨站-安全防御-CSP XSS跨站-安全防御-HttpOnly XSS跨站-安全防御-XSSFilter(过滤器的意思) 1、无任何过滤 2、实体化 输入框没有 3、全部实体化 利用标签事件 单引号闭合 4、全部实体化 利用标签事件 双引号闭合 5、事件关键字过滤 利用其他标签调用 双引号闭合…

物联网监控可视化是什么?部署物联网监控可视化大屏有什么作用?

随着物联网技术的深入应用&#xff0c;物联网监控可视化成为了企业数字化转型的关键环节。物联网监控可视化大屏作为物联网监控平台的重要组成部分&#xff0c;能够实时展示物联网设备的运行状态和数据&#xff0c;为企业管理决策和运维监控提供了有力的支持。今天&#xff0c;…

【OpenBayes 官方教程】在模型训练中使用子域名访问服务

本教程主要为大家讲解 OpenBayes 上如何在模型训练中使用子域名访问服务&#xff0c;新朋友点击下方链接注册后&#xff0c;即可获得 4 小时 RTX 4090 5 小时 CPU 的免费使用时长哦&#xff01; 注册链接 https://openbayes.com/console/signup?ryuudi_nBBThttps://openbay…

matlab及其在数字信号处理中的应用001:软件下载及安装

目录 一&#xff0c;matlab的概述 matlab是什么 matlab适用于的问题 matlab的易扩展性 二&#xff0c;matlab的安装 1&#xff0c;解压所有压缩文件 2&#xff0c;解压镜像压缩文件 3&#xff0c;运行setup.exe 4&#xff0c;开始安装 5&#xff0c;不要运行软件…

慧天【HTWATER】:水文水动力模型的革命性工具,城市内涝的精准解决方案

城市内涝水文水动力模型介绍 在城市排水防涝规划过程中&#xff0c;水文水动力耦合模型已经成为一种不可或缺的分析工具。在模型建立、城市内涝风险评估、排水系统性能诊断以及海绵城市规划等方面&#xff0c;内涝耦合模型提供了相应的模拟及分析工具&#xff1a; 1.1丰富的数…

Windows系统下ESP-IDF环境的搭建

第一步&#xff1a;clone项目&#xff0c;建议是下到"Desktop\esp-idf"的路径 下载地址&#xff1a;https://github.com/espressif/esp-idf 第二步&#xff1a;在VSCode下载ESP-IDF插件 第三步&#xff1a;在Setup页面选择第一个选项EXPRESS 参考文档&#xff1a;h…

visual studio报:引发的异常:“System.DllNotFoundException”(位于 ConsoleCAN1.exe 中)

最近在重构CAN通信的代码&#xff0c;把论文中的java转为C#实现&#xff0c;由于某种原因&#xff0c;java不能复现&#xff0c;所以转为c#。 然而c#的重构过程遇到许多问题&#xff0c;因为两种语言的编程方式、线程等等实现上有所差异。 其中一个错误&#xff1a; 引发的异…

java数据结构与算法刷题-----LeetCode34. 在排序数组中查找元素的第一个和最后一个位置

java数据结构与算法刷题目录&#xff08;剑指Offer、LeetCode、ACM&#xff09;-----主目录-----持续更新(进不去说明我没写完)&#xff1a;https://blog.csdn.net/grd_java/article/details/123063846 文章目录 二分查找 二分查找 解题思路&#xff1a;时间复杂度O( l o g 2 …