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而产生错误。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

简单了解策略模式

什么是策略模式&#xff1f; 策略模式提供生成某一种产品的不同方式 Strategy策略类定义了某个各种算法的公共方法&#xff0c;不同的算法类通过继承Strategy策略类&#xff0c;实现自己的算法 Context的作用是减少客户端和Strategy策略类之间的耦合&#xff0c;客户端只需要…

ubuntu 连接 校园网

​ 认证修改为 Protected EAP(PEAP) CA 证书 勾选 No CA certificate is required 输入用户名和密码 连接成功 ​

【火猫TV】西甲:巴萨中后场大洗牌,两位新人或被放弃!

巴萨本赛季已经来到了最关键的时刻,联赛中他们要想办法缩小与皇马的差距,欧冠联赛则要和大巴黎争夺四强名额。不过球队在转会市场上的操作非常频繁,在转会资金有限的情况下,他们已经准备了多套引援策略,其中对于中后场的打造可能会成为今夏的工作重心。比如后防核心阿劳霍就被多…

二维码门楼牌管理应用平台建设:智能匹配与高效管理

文章目录 前言一、二维码门楼牌管理应用平台的意义二、地址坐标校验的重要性三、对外采数据匹配校验的实现方式四、智能匹配与人工审核的结合五、二维码门楼牌管理应用平台的前景展望 前言 随着城市化进程的加速&#xff0c;门楼牌管理成为城市治理中不可或缺的一环。传统的门…

java电话号码的字母组合(力扣Leetcode17)

电话号码的字母组合 力扣原题链接 问题描述 给定一个仅包含数字 2-9 的字符串&#xff0c;返回所有它能表示的字母组合。答案可以按 任意顺序 返回。 给出数字到字母的映射如下&#xff08;与电话按键相同&#xff09;。注意 1 不对应任何字母。 示例 示例 1&#xff1a;…

HarmonyOS 应用开发之启动/停止本地PageAbility

启动本地PageAbility PageAbility相关的能力通过featureAbility提供&#xff0c;启动本地Ability通过featureAbility中的startAbility接口实现。 表1 featureAbility接口说明 接口名接口描述startAbility(parameter: StartAbilityParameter)启动Ability。startAbilityForRes…

GPT提示词分享 —— 智能域名生成器

提示词&#x1f447; 我希望你能充当一个聪明的域名生成器。我将告诉你我的公司或想法是什么&#xff0c;你将根据我的提示回复我一份域名备选清单。你只需回复域名列表&#xff0c;而不是其他。域名应该是最多 7-8 个字母&#xff0c;应该简短但独特&#xff0c;可以是朗朗上口…

【物联网项目】基于ESP8266的家庭灯光与火情智能监测系统——文末完整工程资料源码

目录 系统介绍 硬件配置 硬件连接图 系统分析与总体设计 系统硬件设计 ESP8266 WIFI开发板 人体红外传感器模块 光敏电阻传感器模块 火焰传感器模块 可燃气体传感器模块 温湿度传感器模块 OLED显示屏模块 系统软件设计 温湿度检测模块 报警模块 OLED显示模块 …

STM32H743驱动SSD1309(5)

接前一篇文章&#xff1a;STM32H743驱动SSD1309&#xff08;4&#xff09; 三、命令说明 10. 设置BANK0的对比度控制&#xff08;81h&#xff09; 此命令设置显示器的对比度设置。该芯片具有从00h到FFh的256个对比度阶跃。segment输出电流随着对比度阶跃值的增加而增加。 示例…

CMOS逻辑门电路主要技术参数

传输延迟时间 由于电极之间以及电极与衬底之间存在寄生电容&#xff0c;并且输出端通常也存在负载电容。当输入信号跳变时&#xff0c;由于电容的充放电&#xff0c;输出电压的变化必然滞后与输入电压的变化。 门电路传输延迟波形图如下&#xff1a; 通常CMOS逻辑门电路输出端…

哔哩哔哩直播姬有线投屏教程

1 打开哔哩哔哩直播姬客户端并登录(按下图进行操作) 2 用usb连接电脑(若跳出安装驱动的弹窗点击确定或允许),usb的连接方式为仅充电 不要更改usb的连接方式(不然电脑会死机需要重启),此时电脑识别不到该手机设备(因为电脑把它识别为投屏设备) 想要正常连接电脑进行文件传输就按…

日志集中审计系列(4)--- LogAuditor接收IPS设备日志

日志集中审计系列(4)--- LogAuditor接收IPS设备日志 前言拓扑图设备选型组网需求配置思路操作步骤结果验证前言 近期有读者留言:“因华为数通模拟器仅能支持USG6000V的防火墙,无法支持别的安全产品,导致很多网络安全的方案和产品功能无法模拟练习,是否有真机操作的实验或…

Vue3:快速上手路由器

本人在B站上关于vue3的尚硅谷的课程&#xff0c;以下是整理一些笔记。 一.路由器和路由的概念 在 Vue 3 中&#xff0c;路由&#xff08;Router&#xff09;和路由器&#xff08;Router&#xff09;是两个相关但不同的概念。 1. 路由&#xff08;Router&#xff09;&#xff…

Java面试题第二季

一、JUC多线程及高并发 1.谈谈你对volatile的理解 线程安全获得保证 1.volatile是Java虚拟机提供的轻量级的同步机制 保证可见性 不保证原子性 1号和2号线程同时修改各自工作空间中的内容&#xff0c;因为可见性&#xff0c;需要重写入内存&#xff0c;但是1号在写入的时候…

如何保证redis里的数据都是热点数据

MySQL 里有 2000w 数据&#xff0c;Redis 中只存 20w 的数据&#xff0c;如何保证 redis 中的数据都是热点数据&#xff1f; 1.Redis 过期删除策略 1&#xff09;惰性删除:放任键过期不管&#xff0c;但是每次从键空间中获取键时&#xff0c;都检查取得的键是否过期&#xff0c…

Mac上Matlab_R2023b ARM 版 启动闪退(意外退出)解决方法

安装好后&#xff0c;使用 "libmwlmgrimpl.dylib" 文件替换掉"/Applications/Matlab_R2023b.app/bin/maca64/matlab_startup_plugins/lmgrimpl"文件夹下的同名文件 在终端下执行如下命令&#xff1a; codesign --verbose --force --deep -s - /Applicat…

简易TCP服务器通信、IO多路复用(select、poll、epoll)以及reactor模式。

网络编程学习 简单TCP服务器通信TCP三次握手和四次挥手三次握手&#xff08;如下图&#xff09;常见问题&#xff1f; 四次挥手 client和server通信写法server端client端 通信双方建立连接到断开连接的状态转换怎么应对多用户连接&#xff1f;缺点 IO多路复用select优缺点 poll…

物联网学习2、MQTT 发布/订阅模式介绍

MQTT 发布/订阅模式 发布订阅模式&#xff08;Publish-Subscribe Pattern&#xff09;是一种消息传递模式&#xff0c;它将发送消息的客户端&#xff08;发布者&#xff09;与接收消息的客户端&#xff08;订阅者&#xff09;解耦&#xff0c;使得两者不需要建立直接的联系也不…

算法题2两数相加

给你两个 非空 的链表&#xff0c;表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的&#xff0c;并且每个节点只能存储 一位 数字。 请你将两个数相加&#xff0c;并以相同形式返回一个表示和的链表。你可以假设除了数字 0 之外&#xff0c;这两个数都不会以 0 开…

动态规划之方格取数

方格取数 动态规划&#xff0c;数字三角形模型 题目链接 https://www.luogu.com.cn/problem/P1004 题目描述 解法一 O ( n 4 ) O(n^4) O(n4) #include<bits/stdc.h> using namespace std; int n, i, j, l, k, x, y, s; int d[55][55], f[55][55][55][55]; int main()…