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.mzph.cn/news/780925.shtml

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

相关文章

Cocos Creator 常见问题记录

目录 问题1、精灵图九宫格&#xff0c;角度不拉伸 问题2、BlockInputEvents 防止透屏 问题1、精灵图九宫格&#xff0c;角度不拉伸 点击编辑&#xff0c;拖拽到可变区域 问题2、BlockInputEvents 防止透屏

【独立开发前线】Vol.26 【独立开发产品】吉光卡片-让你的文字变得酷炫起来

今天给大家分享一下 独立开发前线 社区成员张小吉 的作品 吉光卡片&#xff1b; 这是一款iOS的APP&#xff0c;下载&#xff1a;吉光卡片&#xff0c;主要功能是帮你制作酷炫的文字卡片&#xff0c;用精美的卡片让你的文字生动起来。 展示效果如下&#xff1a; 你可以用它制作…

【公示】2023年度青岛市级科技企业孵化器拟认定名单

根据《青岛市科技企业孵化器管理办法》&#xff08;青科规〔2023〕1号&#xff09;&#xff08;以下简称《管理办法》&#xff09;、《关于开展2023年度市级科技企业孵化器认定申报工作的通知》&#xff0c;经申报受理、区市推荐、形式审查、专家评审及现场核查等程序&#xff…

【笔记】动⼿学深度学习(花书)|| Aston Zhang Mu Li Zachary C. LiptonAlexander J. Smola

系列文章目录 提示&#xff1a;这里可以添加系列文章的所有文章的目录&#xff0c;目录需要自己手动添加 前言 第一章 深度学习简介 第二章 P 提示&#xff1a;写完文章后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 系列文章目录前言本书…

BasicVSR++模型转JIT并用c++libtorch推理

BasicVSR模型转JIT并用clibtorch推理 文章目录 BasicVSR模型转JIT并用clibtorch推理安装BasicVSR 环境1.下载源码2. 新建一个conda环境3. 安装pytorch4. 安装 mim 和 mmcv-full5. 安装 mmedit6. 下载模型文件7. 测试一下能否正常运行 转换为JIT模型用c libtorch推理效果 安装Ba…

malloc是如何分配内存|malloc(1)分配多大内存|free释放内存,会还给操作系统吗?

前言 大家好&#xff0c; 我jiantaoyab&#xff0c;这篇文章给大家介绍mallo和free面试中常问到的问题。 malloc是如何分配内存的&#xff1f; 如果用户分配的内存小于128KB&#xff0c;则通过brk()申请内存 如果用户分配的内存大于128KB&#xff0c;则通过mmap()申请内存 简…

数据分析之POWER Piovt的KPI设置

内容总结&#xff1a; 1.两个表格关联不上&#xff1a;需要添加辅助列&#xff0c;建立关联 2.添加辅助列后还关联不上&#xff1a;将虚线变为实线 3.根据需求要增加一些度量值 4.设置KPI后&#xff0c;绝对值选1后设定百分比 5.在透视表里面加入KPI状态 导入所关联的数据后建立…

游戏领域AI智能视频剪辑解决方案

游戏行业作为文化创意产业的重要组成部分&#xff0c;其发展和创新速度令人瞩目。然而&#xff0c;随着游戏内容的日益丰富和直播文化的兴起&#xff0c;传统的视频剪辑方式已难以满足玩家和观众日益增长的需求。美摄科技&#xff0c;凭借其在AI智能视频剪辑领域的深厚积累和创…

SQLBolt,一个练习SQL的宝藏网站

知乎上有人问学SQL有什么好的网站&#xff0c;这可太多了。 我之前学习SQL买了本SQL学习指南&#xff0c;把语法从头到尾看了个遍&#xff0c;但仅仅是心里有数的程度&#xff0c;后来进公司大量的写代码跑数&#xff0c;才算真真摸透了SQL&#xff0c;知道怎么调优才能最大化…

SpringBoot登录校验(三)

​​​​​​​SpringBoot 登录认证&#xff08;一&#xff09;-CSDN博客 SpringBoot 登录认证&#xff08;二&#xff09;-CSDN博客 SpringBoot登录校验&#xff08;三&#xff09;-CSDN博客 前面我们介绍了传统的会话跟踪技术cookie和sesstion&#xff0c;本节讲解令牌技术…

Ubuntu20.04LTS+uhd3.15+gnuradio3.8.1源码编译及安装

文章目录 前言一、卸载本地 gnuradio二、安装 UHD 驱动三、编译及安装 gnuradio四、验证 前言 本地 Ubuntu 环境的 gnuradio 是按照官方指导使用 ppa 的方式安装 uhd 和 gnuradio 的&#xff0c;也是最方便的方法&#xff0c;但是存在着一个问题&#xff0c;就是我无法修改底层…

HarmonyOS实战开发-如何实现一个简单的电子相册应用开发

介绍 本篇Codelab介绍了如何实现一个简单的电子相册应用的开发&#xff0c;主要功能包括&#xff1a; 实现首页顶部的轮播效果。实现页面跳转时共享元素的转场动画效果。实现通过手势控制图片的放大、缩小、左右滑动查看细节等效果。 相关概念 Swiper&#xff1a;滑块视图容…

java多线程中的阻塞队列

一、普通不阻塞队列 还记得队列我们如何实现吗&#xff1f;我们用的是循环队列的方式&#xff0c;回一下&#xff1a; 描述&#xff1a;开始tail和head指针都指向最开始位置&#xff0c;往里面添加元素tail&#xff0c;出元素head 初始状态&#xff1a; put元素后状态 take…

账号微服务短信验证码发送工具单元测试

账号微服务短信验证码发送工具单元测试 注意sms的 app-code #----------sms短信配置-------------- sms:app-code: dd7829bedfaf4373875aa91abba82523template-id: JM1000372package net.xdclass.config;import org.springframework.context.annotation.Bean; import org.spri…

ROS 2边学边练(4)-- 何为主题(topics)

概念 主题是一种节点间的通信方式&#xff0c;某个节点充当发布特定&#xff08;主题&#xff09;消息&#xff08;数据&#xff09;的角色&#xff0c;另外一些节点则可以订阅接收该特定&#xff08;主题&#xff09;消息&#xff08;数据&#xff09;。两者&#xff0…

在ubuntu上搭建系统监控系统

大纲 数据生产方安装和运行验证 数据收集、存储和分发方下载和解压修改配置运行验证 数据消费方下载和运行验证新增数据源新增看板关联看板和数据源效果展现 参考资料 在一个监控系统中&#xff0c;一定会有“数据生产方”和“数据消费方”存在。“数据生产方”用于产出需要监控…

Android MediaRecorder

AndroidManifest.xml中添加权限标记 <uses-permission android:name"android.permission.RECORD_AUDIO"/> 动态添加权限MainActivity requestPermissions(new String[]{Manifest.permission.CAMERA,Manifest.permission.RECORD_AUDIO},100); 创建MediaReco…

Flask学习(五):session相关流程

流程图如下图所示&#xff1a; 调用相关类如下图所示&#xff1a; 相关代码如下&#xff1a; from flask import Flask, sessionapp Flask(__name__)1. 加密会话数据&#xff1a;在 Flask 中&#xff0c;会话数据存储在客户端的 cookie 中。设置 app.secret_key 可以加密会话…

OLED模块

OLED模块 综述&#xff1a;本篇文章简要讲述了oled的定义&#xff0c;两种oled的引脚和接线情况、iic通讯协议、spi通讯协议、OLED代码引用和注意事项。 1.定义 OLED&#xff08;Organic Light-Emitting Diode&#xff09;模块是一种使用有机发光二极管作为显示元素的显示模…

DFS:二叉树的深搜与回溯

一、计算布尔二叉树的值 . - 力扣&#xff08;LeetCode&#xff09; class Solution { public:bool evaluateTree(TreeNode* root) {if(root->leftnullptr) return root->val0?false:true; bool left evaluateTree(root->left);bool rightevaluateTree(root->rig…