深入解析主线程退出与子线程管理:何时 Join(),何时 Detach()?
在多线程编程中,主线程退出时如何正确管理子线程是一个关键问题。如果子线程没有 Join() 或 Detach(),不同的操作系统会有不同的行为,可能导致内存泄漏、资源竞争、甚至程序崩溃。本文将深入探讨主线程退出时子线程的管理策略,并提供最佳实践。
1. 不 Join() 或 Detach(),会发生什么?
如果主线程(进程)退出,而子线程没有正确处理,可能会出现以下几种情况:
🔹 Windows:子线程会被强制终止
-
进程终止时,所有子线程都会被终止,不会有任何清理操作。
-
子线程的析构函数不会执行,可能导致文件未关闭、锁未释放等问题。
-
DLL 中的
DllMain()可能会被调用,影响进程的正常退出。
示例(Windows 下,子线程被强制终止):
#include <windows.h> #include <iostream> DWORD WINAPI ThreadFunction(LPVOID) { while (true) { std::cout << "Running...\n"; Sleep(1000); } return 0; } int main() { HANDLE thread = CreateThread(NULL, 0, ThreadFunction, NULL, 0, NULL); Sleep(3000); // 主线程退出,未 join 也未 detach return 0; }
结果:
-
子线程执行 3 秒后,主线程退出,子线程会被 系统强制终止,不会再执行
"Running..."。
🔹 Linux/macOS:子线程可能成为“僵尸线程”
-
如果主线程退出,而进程仍然存活(如
daemon进程),子线程仍然运行。 -
如果
main()结束但pthread_exit()没有调用,进程可能会崩溃。 -
在某些系统中,子线程可能变成 僵尸线程,占用资源但无法释放。
示例(Linux/macOS 下,子线程可能仍在运行):
#include <pthread.h> #include <unistd.h> #include <iostream> void* ThreadFunction(void*) { while (true) { std::cout << "Thread running...\n"; sleep(1); } return nullptr; } int main() { pthread_t thread; pthread_create(&thread, NULL, ThreadFunction, NULL); sleep(3); // 主线程退出,不 join 也不 detach return 0; }
结果:
-
可能导致“僵尸线程”:主线程退出,但子线程仍然运行,进程可能保持活动状态。
解决方案:
-
使用
pthread_exit(NULL);让进程不崩溃:int main() { pthread_t thread; pthread_create(&thread, NULL, ThreadFunction, NULL); sleep(3); pthread_exit(NULL); // 允许子线程继续运行 } -
使用
pthread_detach()让系统自动清理线程:pthread_detach(thread);
2. 何时 Join()?
Join() 让主线程等待子线程完成,适用于: ✅ 子线程需要正确完成任务(如数据存储、文件写入)。
✅ 子线程修改共享资源,避免访问已释放的内存。
✅ 防止 std::terminate() 触发,避免程序崩溃。
示例:等待子线程完成
std::thread worker([] { std::this_thread::sleep_for(std::chrono::seconds(5)); std::cout << "Thread done\n"; }); worker.join(); // 确保子线程执行完毕
⚠️ 注意:
-
如果
Join()发生在错误的时机(如StopSoon()之前),可能会导致死锁。 -
如果
Join()的线程本身被阻塞,可能导致主线程卡死。
3. 何时 Detach()?
Detach() 让子线程独立运行,主线程退出后仍继续执行,适用于: ✅ 子线程是守护线程(后台任务)(如日志、定时任务)。
✅ 子线程的生命周期独立于主线程(如事件监听)。
✅ 任务调度器(如 ThreadPool)管理子线程,不需要手动 Join()。
示例:后台任务
std::thread worker([] { while (true) { std::this_thread::sleep_for(std::chrono::seconds(1)); std::cout << "Background task running\n"; } }); worker.detach(); // 让子线程独立运行
⚠️ 注意:
-
子线程不能访问局部变量,否则可能访问已释放的资源。
-
适用于真正的后台任务,如 日志记录、监控任务。
4. 进程退出时,哪些情况不需要 Join()?
某些情况下,子线程的生命周期由外部系统或事件驱动,无需 Join():
🔹 1. 守护线程
后台任务(如日志、心跳检测)应该在后台持续运行,即使主线程退出。
void BackgroundTask() { while (true) { std::this_thread::sleep_for(std::chrono::seconds(1)); std::cout << "Background task running\n"; } } int main() { std::thread bg_thread(BackgroundTask); bg_thread.detach(); // 主线程不管它,让它独立运行 return 0; }
🔹 2. 事件驱动线程
如果子线程的生命周期由事件控制,可以用 Condition Variable 或 Event 让子线程自己退出。
HANDLE stop_event = CreateEvent(NULL, TRUE, FALSE, NULL); DWORD WINAPI WorkerThread(LPVOID) { while (WaitForSingleObject(stop_event, 0) == WAIT_TIMEOUT) { // 执行任务 } return 0; } int main() { HANDLE thread = CreateThread(NULL, 0, WorkerThread, NULL, 0, NULL); Sleep(3000); SetEvent(stop_event); // 通知子线程退出 CloseHandle(thread); return 0; }
5. 结论
| 情况 | Windows | Linux/macOS (POSIX) |
|---|---|---|
主线程退出,子线程未 Join() 或 Detach() | 进程终止,子线程被杀死 | 进程可能仍在运行,或子线程变成“僵尸线程” |
Join() 解决方案 | 确保子线程完成后退出 | 确保子线程完成后退出 |
Detach() 解决方案 | 让子线程自己运行,但可能导致资源泄漏 | 让子线程自己运行,但可能访问已释放资源 |
最佳实践
✅ 如果子线程必须完成任务 → Join()
✅ 如果子线程是后台任务 → Detach()
✅ 如果 Detach(),确保子线程不访问已释放的资源
相关文章:
深入解析主线程退出与子线程管理:何时 Join(),何时 Detach()?
在多线程编程中,主线程退出时如何正确管理子线程是一个关键问题。如果子线程没有 Join() 或 Detach(),不同的操作系统会有不同的行为,可能导致内存泄漏、资源竞争、甚至程序崩溃。本文将深入探讨主线程退出时子线程的管理策略,并提…...
AWS API Gateway Canary部署实战:Lambda到ECS的平滑迁移指南
在云原生架构中,如何实现服务平滑迁移是一个常见挑战。本文将详细介绍如何利用AWS API Gateway的Canary部署功能,实现从Lambda函数到ECS服务的无缝迁移,同时保证客户端无感知并提供便捷的回退机制。 一、迁移方案概述 在本方案中,我们将实现以下目标: 将现有Lambda服务平…...
Docker学习--容器操作相关命令--docker export 命令
docker export 命令的作用: 用于将 Docker 容器的文件系统导出为一个 tar 归档文件。主要用于备份或迁移容器的文件系统,而不包括 Docker 镜像的所有层和元数据。 语法: docker export [参数选项] CONTAINER(要操作的容器&#x…...
【Easylive】获取request对象的两种方式
【Easylive】项目常见问题解答(自用&持续更新中…) 汇总版 1. 通过方法参数直接注入(Spring MVC 推荐) 在 Controller 方法中直接声明 HttpServletRequest 参数,Spring 会自动注入当前请求的 request 对象&#…...
FOC 控制笔记【三】磁链观测器
一、磁链观测器基础 1.1 什么是磁链 磁链(magnetic linkage)是电磁学中的一个重要概念,指导电线圈或电流回路所链环的磁通量。单位为韦伯(Wb),又称磁通匝。 公式为: 线圈匝数 穿过单匝数的…...
SpringBoot项目读取自定义的配置文件
先说使用场景: 开发时在resource目录下新建一个 config 文件夹, 在里面存放 myconf.properties 文件, 打包后这个文件会放到与jar包同级的目录下, 如下图 关键点:自定义的文件名(当然后缀是.properties),自定义的存放路径。 主要的要求是在打包后运行过…...
UniApp快速表单组件
环境:vue3 uni-app 依赖库:uview-plus、dayjs 通过配置项快速构建 form 表单 使用 <script setup>import CustomCard from /components/custom-card.vue;import { ref } from vue;import CustomFormItem from /components/form/custom-form-it…...
在PyCharm 中免费集成Amazon CodeWhisperer
CodeWhisperer 是Amazon发布的一款免费的AI 编程辅助小工具,可在你的集成开发环境(IDE)中生成实时单行或全函数代码建议,帮助你快速构建软件。简单来说,Amazon CodeWhisperer就是你写一段注释(支持中文&…...
语音克隆(Voice Cloning)
要将文字转化为“自己声音”的音频,需要用到语音克隆(Voice Cloning)技术。这种技术通常要求用户提供一定量的语音样本(几分钟到几小时不等),然后通过 AI 模型生成与你声音相似的音频。目前市面上完全免费且…...
[7-02-02].第15节:生产经验 - 消费者相关操作
Kafka笔记大纲 五、生产经验——分区的分配以及再平衡: 4.1.生产经验——分区的分配以及再平衡 4.2.参数: 5.4.1 Range 以及再平衡...
Matlab_Simulink中导入CSV数据与仿真实现方法
前言 在Simulink仿真中,常需将外部数据(如CSV文件或MATLAB工作空间变量)作为输入信号驱动模型。本文介绍如何高效导入CSV数据至MATLAB工作空间,并通过From Workspace模块实现数据到Simulink的精确传输,适用于运动控制…...
vue3大屏适配
最近写大屏,发现适配真的好难统一,不是这有问题就是那有问题,要不然页面拉伸的就变形了,在网上找到了一个好用的插件,暂时用起来没问题,如果后续有问题或者大家有什么好的想法可以在评论区说一下。 插件 bi…...
文件操作与IO—File类
目录 1 属性 2 构造方法 3 常用方法 4 示例代码 1 属性 修饰符与类型 属性 含义 static String pathSeparator 依赖于系统的路径分隔符,String类型的表示 static char pathSeparator 依赖于系统的路径分隔符,char类型的表示 2 构造方法 构造…...
音频进阶学习二十四——IIR滤波器设计方法
文章目录 前言一、滤波器设计要求1.选频滤波器种类2.通带、阻带、过度带3.滤波器设计指标 二、IIR滤波器的设计过程1.设计方法2.常见的模拟滤波器设计1)巴特沃斯滤波器(Butterworth Filter)2)切比雪夫滤波器(Chebyshev…...
OpenBMC:BmcWeb 处理http请求2 查找路由对象
OpenBMC:BmcWeb 处理http请求1 生成Request和AsyncResp对象_bmc web-CSDN博客 当接收到http请求,并且完成解析后,调用了App::handle处理请求 而App::handle又调用了router.handle(req, asyncResp);来处理请求 1.Router::handle void handle(const std::shared_ptr<Requ…...
MVC编程
MVC基本概述 例子——显示本地文件系统结构 先分别拖入ListView,TableView,TreeView 然后在进行布局 在widget.cpp 结果 mock测试 1,先加入json测试对象 2.创建后端目录 3,在src添加新文件 在models文件夹里 在mybucket.h,添加测试用例的三个字段 4.在…...
怎么对asp.web api进行单元测试?
在 ASP.NET Web API 中进行单元测试是一种确保代码质量和功能正确性的重要实践。单元测试的重点是针对 API 控制器中的逻辑进行测试,而不依赖于外部依赖(如数据库、文件系统或网络请求)。以下是实现 ASP.NET Web API 单元测试的步骤和方法&am…...
Qt进阶开发:对象树与拥有权
文章目录 一、对象树的概念二、对象拥有权(Ownership)三、Qt Widgets 中的特殊情况四、对象树与拥有权的实例 一、对象树的概念 在 Qt 中,对象树(Object Tree)与对象的拥有权(Ownership)密切相…...
Django:构建高性能Web应用
引言:为何选择Django? 在当今快速发展的互联网时代,Web应用的开发效率与可维护性成为开发者关注的核心。Django作为一款基于Python的高级Web框架,以其"开箱即用"的特性、强大的ORM系统、优雅的URL路由设计,…...
C语言基础系列【32】指针进阶5:指针与常量
博主介绍:程序喵大人 35- 资深C/C/Rust/Android/iOS客户端开发10年大厂工作经验嵌入式/人工智能/自动驾驶/音视频/游戏开发入门级选手《C20高级编程》《C23高级编程》等多本书籍著译者更多原创精品文章,首发gzh,见文末👇…...
TS 中 interface 和 type 详解
在 TypeScript 中,interface 和 type 都可以用来定义类型,但它们有一些关键的区别。以下是它们的详细对比: 1. 基本定义 interface 用于声明对象的形状(属性和方法),是面向对象编程中“接口”概念的体现。 …...
文法 2025/3/3
文法的定义 一个文法G是一个四元组:G(,,S,P) :一个非空有限的终极符号集合。它的每个元素称为终极符号或终极符,一般用小写字母表示。终极符号是一个语言不可再分的基本符号。 :一个非空有限的非终极符号集合。它的每个元素称为…...
蚂蚁集团主导的ISO密码学国际标准立项,纳入国产算法
蚂蚁集团主导的ISO密码学国际标准 ISO 25330-3 立项, 国产算法Ferret成为标准方案。 近日,在美国弗吉尼亚州举行的 ISO/IEC JTC 1/SC 27 全体会议上,ISO/IEC 25330第三部分《Information Security — Oblivious Transfer — Part 3: Obliv…...
nginx的用户认证
[rootserver100 html]# htpasswd -cm /usr/local/nginx/.htpasswd lee 创建用户给密码 编写nginx的配置文件 [rootserver100 html]# echo lee > /data/web/lee/index.html 写入实验内容 访问成功 用户访问认证的设定 用户认证的设定成功...
为什么要指针压缩,为什么能指针压缩?原理是什么?
指针压缩(Compressed Oops)的原理与实现 指针压缩是 JVM 在 64 位环境 下优化内存占用的关键技术,通过减少对象指针的内存开销,提升缓存利用率和性能。以下是其核心原理与设计细节: 一、为什么要指针压缩?…...
pyproj 库中Geod类—geod.npts()方法讲解
二、pyproj 库中Geod类geod.npts()方法讲解 geod.npts() 方法用于在起点和终点之间生成沿测地线均匀分布的中间点 示例演示 from pyproj import Geod# 初始化 WGS84 椭球体 geod Geod(ellps"WGS84")# 起点:北京 (116.4, 39.9),终点ÿ…...
使用DeepSeek API进行情感分析:超简单
文章目录 1. 引言1.1 情感分析概述1.2 为什么选择DeepSeek API1.3 本文目标 2. 技术方案对比2.1 传统情感分析方法2.2 基于LLM的方法DeepSeek API优势 3. DeepSeek 情感分析实战3.1 Few-shot Learning方法3.2 完整的DeepSeek API调用示例3.3 案例演示 4. DeepSeek开发情感分析工…...
一套SaaS多租户医疗云his源码,基于云计算的医院信息管理系统(云HIS)
基于云计算的医院信息管理系统(云HIS),通过SaaS服务模式提供。这种云HIS系统设计考虑了模板化、配置化、智能化和可扩展性,覆盖了基层医疗机构的核心工作流程,并且能够与监管系统无缝对接,满足未来的扩展需…...
数据处理与机器学习入门
一、数据处理概述 数据处理是通过统计学、机器学习和数据挖掘方法从原始数据中提取有价值信息的过程。数据处理的目标是将杂乱无章的原始数据转化为可用于分析和建模的结构化数据。对于小规模数据处理,常用工具分为两类: • 可视化分析工具:…...
Qt中绘制不规则控件
在Qt中绘制不规则控件可通过设置遮罩(Mask)实现。以下是详细步骤: 继承目标控件:如QPushButton或QWidget。重写resizeEvent:当控件大小变化时,更新遮罩形状。创建遮罩区域:使用QRegion或QPain…...
