Linux线程(三)终止线程与回收线程详解

news2024/11/27 8:30:46

1.终止线程

在示例代码,我们在新线程的启动函数(线程 start 函数)new_thread_start()通过 return 返回之后,意味着该线程已经终止了,除了在线程 start 函数中执行 return 语句终止线程外,终止线程的方式还有多种,可以通过如下方式终止线程的运行:

  • 线程的 start 函数执行 return 语句并返回指定值,返回值就是线程的退出码;
  • 线程调用 pthread_exit()函数;
  • 调用 pthread_cancel()取消线程(将在 6 小节介绍);

如果进程中的任意线程调用 exit()、_exit()或者_Exit(),那么将会导致整个进程终止,这里需要注意!

pthread_exit()函数将终止调用它的线程,其函数原型如下所示:

#include <pthread.h>

void pthread_exit(void *retval);

使用该函数需要包含头文件<pthread.h>。

参数 retval 的数据类型为 void *,指定了线程的返回值、也就是线程的退出码,该返回值可由另一个线程通过调用 pthread_join()来获取;同理,如果线程是在 start 函数中执行 return 语句终止,那么 return 的返回值也是可以通过 pthread_join()来获取的。参数 retval 所指向的内容不应分配于线程栈中,因为线程终止后,将无法确定线程栈的内容是否有效;

出于同样的理由,也不应在线程栈中分配线程 start 函数的返回值。

调用 pthread_exit()相当于在线程的 start 函数中执行 return 语句,不同之处在于,可在线程 start 函数所调用的任意函数中调用 pthread_exit()来终止线程。如果主线程调用了 pthread_exit(),那么主线程也会终止,但其它线程依然正常运行,直到进程中的所有线程终止才会使得进程终止。

使用示例

//示例代码 11.4.1 pthread_exit()终止线程使用示例

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <unistd.h>

static void *new_thread_start(void *arg) {
 printf("新线程 start\\n");
 sleep(1);
 printf("新线程 end\\n");
 pthread_exit(NULL);
}

int main(void) {
 pthread_t tid;
 int ret;
 ret = pthread_create(&tid, NULL, new_thread_start, NULL);
 if (ret) {
		 fprintf(stderr, "Error: %s\\n", strerror(ret));
		 exit(-1);
 }
 printf("主线程 end\\n");
 pthread_exit(NULL);
 exit(0);
}

新线程中调用 sleep()休眠,保证主线程先调用 pthread_exit()终止,休眠结束之后新线程也调用pthread_exit()终止,编译测试看看打印结果:

正如上面介绍到,主线程调用 pthread_exit()终止之后,整个进程并没有结束,而新线程还在继续运行。

2.回收线程

 在父、子进程当中,父进程可通过 wait()函数(或其变体 waitpid())阻塞等待子进程退出并获取其终止状态,回收子进程资源;而在线程当中,也需要如此,通过调用 pthread_join()函数来阻塞等待线程的终止,并获取线程的退出码,回收线程资源;pthread_join()函数原型如下所示:

#include <pthread.h>

int pthread_join(pthread_t thread, void **retval);

使用该函数需要包含头文件<pthread.h>。

函数参数和返回值含义如下:

thread:

pthread_join()等待指定线程的终止,通过参数 thread(线程 ID)指定需要等待的线程;

retval:

如果参数 retval 不为 NULL,则 pthread_join()将目标线程的退出状态(即目标线程通过pthread_exit()退出时指定的返回值或者在线程 start 函数中执行 return 语句对应的返回值)复制到retval 所指向的内存区域;如果目标线程被 pthread_cancel()取消,则将 PTHREAD_CANCELED 放在retval 中。如果对目标线程的终止状态不感兴趣,则可将参数 retval 设置为 NULL。

返回值:

成功返回 0;失败将返回错误码。

调用 pthread_join()函数将会以阻塞的形式等待指定的线程终止,如果该线程已经终止,则 pthread_join()立刻返回。如果多个线程同时尝试调用 pthread_join()等待指定线程的终止,那么结果将是不确定的。

若线程并未分离(detached,将在 11.6.1 小节介绍),则必须使用 pthread_join()来等待线程终止,回收线程资源;如果线程终止后,其它线程没有调用 pthread_join()函数来回收该线程,那么该线程将变成僵尸线程,与僵尸进程的概念相类似;同样,僵尸线程除了浪费系统资源外,若僵尸线程积累过多,那么会导致应用程序无法创建新的线程。

当然,如果进程中存在着僵尸线程并未得到回收,当进程终止之后,进程会被其父进程回收,所以僵尸线程同样也会被回收。

所以,通过上面的介绍可知,pthread_join()执行的功能类似于针对进程的 waitpid()调用,不过二者之间存在一些显著差别:

  • 线程之间关系是对等的。进程中的任意线程均可调用 pthread_join()函数来等待另一个线程的终止。譬如,如果线程 A 创建了线程 B,线程 B 再创建线程 C,那么线程 A 可以调用 pthread_join()等待线程 C 的终止,线程 C 也可以调用 pthread_join()等待线程 A 的终止;这与进程间层次关系不同,父进程如果使用 fork()创建了子进程,那么它也是唯一能够对子进程调用 wait()的进程,线程之间不存在这样的关系。
  • 不能以非阻塞的方式调用 pthread_join()。对于进程,调用 waitpid()既可以实现阻塞方式等待、也可以实现非阻塞方式等待。

使用示例

//示例代码 11.5.1 pthread_join()等待线程终止
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <unistd.h>

static void *new_thread_start(void *arg) {
	 printf("新线程--running\\n");
	 for ( ; ; )
			 sleep(1);
	 return (void *)0; 
}

int main(void) {
	 pthread_t tid;
	 void *tret;
	 int ret;

	 /* 创建新线程 */
	 ret = pthread_create(&tid, NULL, new_thread_start, NULL);
	 if (ret) {
			 fprintf(stderr, "pthread_create error: %s\\n", strerror(ret));
			 exit(-1);
	 }
	 sleep(1);

	 /* 向新线程发送取消请求 */
	 ret = pthread_cancel(tid);
	 if (ret) {
			 fprintf(stderr, "pthread_cancel error: %s\\n", strerror(ret));
			 exit(-1);
	 }

	 /* 等待新线程终止 */
	 ret = pthread_join(tid, &tret);
	 if (ret) {
		 fprintf(stderr, "pthread_join error: %s\\n", strerror(ret));
		 exit(-1);
	 }

	 printf("新线程终止, code=%ld\\n", (long)tret);
	 exit(0);
}

主线程调用 pthread_create()创建新线程之后,新线程执行 new_thread_start()函数,而在主线程中调用pthread_join()阻塞等待新线程终止,新线程终止后,pthread_join()返回,将目标线程的退出码保存在*tret 所指向的内存中。

测试结果如下:

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

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

相关文章

Qt基础之四十八:按钮为何会有点击效果

我们从一个最简单的Window API窗口程序开始说起。 一.一个最简单的Window API窗口程序 #include <windows.h> #include <wingdi.h> // 声明窗口过程函数 LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);int WINAPI WinMain(HI…

Windows程序包管理器WinGet的使用方法

Windows 程序包管理器 WinGet 命令行工具作为应用安装程序的一部分在 Windows 11 和现代版本的 Windows 10 上提供。 WinGet 工具的当前预览版支持以下命令: 命令说明info显示有关系统的元数据&#xff08;版本号、体系结构、日志位置等&#xff09;。 有助于进行故障排除。i…

电磁兼容(EMC):整改案例(四)人体对EFT测试影响有多大?

目录 1. 异常现象 2. 原因分析 3. 整改方案 4. 总结 1. 异常现象 某产品按GB/T 17626.4标准进行电快速瞬变脉冲群测试&#xff0c;测试条件为&#xff1a;频率5kHz/100kHz&#xff0c;测试电压L&#xff0c;N线间2kV&#xff0c;L&#xff0c;N线对PE线4kV。测试过程中需要…

Linux学习笔记(五):shell脚本,强大的文本处理工具awk,sed

Linux学习笔记&#xff08;五&#xff09;&#xff1a;shell脚本&#xff0c;awk&#xff0c;sed&#xff0c;服务管理 1. Shell 脚本 Shell 脚本是一种使用 Shell 编写的脚本&#xff0c;它可以在 Linux 系统中执行各种任务。 1.1 变量 声明变量&#xff1a; 使用 export 命令…

C++容器类型内置函数随笔

vector 容器 添加数据 vector <int> v ; v.push_back(数据); 访问数据的两个迭代器b.begin()和v.end() vector <int> :: iterator it_begin v. begin(); //容器起始位置元素的指针vector <int> :: iterator it_end v.end(); //指向容器最后一个元素下一…

FinOps三人行:云计算时代的FinOps 反模式和SRE(文字+视频版)

简介 9月20日&#xff0c;由SRE专委会和雅菲奥朗主办的“FinOps三人行&#xff1a;云计算时代的FinOps 反模式和SRE”在线研讨会成功举办&#xff0c;三位业界专家雅菲奥朗刘峰老师、易点天下董金老师和辛诺科技Larry老师齐聚一堂&#xff0c;聚焦于云计算环境下的并购模式、运…

Python编码规范与常见问题纠正

Python编码规范与常见问题纠正 Python 是一种以简洁和易读性著称的编程语言&#xff0c;因此&#xff0c;遵循良好的编码规范不仅能使代码易于维护&#xff0c;还能提升代码的可读性和可扩展性。编写规范的 Python 代码也是开发者职业素养的一部分&#xff0c;本文将从 Python…

Java-数据结构-Map和Set(三)-习题 o(´^`)o

目录 ❄️一、习题一(只出现一次的数字)&#xff1a; ❄️二、习题二(随机链表的复制)&#xff1a; ❄️三、习题三(宝石与石头)&#xff1a; ❄️四、习题四(旧键盘)&#xff1a; ❄️五、习题五(前k个高频单词)&#xff1a; ❄️总结&#xff1a; ❄️一、习题一(只出现一…

【Nacos架构 原理】内核设计之Nacos一致性协议

文章目录 Nacos一致性协议为什么需要一致性协议Nacos选择了Raft&#xff08;强一致性&#xff09;&Distro&#xff08;最终一致性&#xff09;服务发现角度配置管理角度 Nacos自研Distro协议背景设计思想数据初始化数据校验写操作读操作 Nacos一致性协议 为什么需要一致性…

大模型笔记05--coze经典案例分析

大模型笔记05--coze经典案例分析 介绍经典案例分析抖音视频转小红书文案艺术照 & 卡通照片助手艺术照图像流卡通照片图像流多功能图像助手 注意事项说明 介绍 扣子是新一代 AI 应用开发平台&#xff0c;具备完善的生态系统&#xff0c;是国内最出色的AI平台之一。用好coze…

C/C++/EasyX ——入门图形编程(2)

【说明】这一篇的内容都是很基础的&#xff0c;所以内容会很多&#xff0c;具体现在也不知道要写多少&#xff0c;先写下去吧&#xff0c;新手小白们都不用担心&#xff0c;这个内容不会很难&#xff0c;因为我也是从一无所知过来的&#xff0c;很好入门的&#xff0c;&#xf…

游览器输入URL并Enter时都发生了什么 面试完美回答

文章目录 前言URL解析DNS解析**浏览器缓存****操作系统缓存**&#xff1a;**路由器缓存**&#xff1a;ISP&#xff08;Internet service provider&#xff09;缓存DNS递归解析IP地址的获取缓存结果 建立TCP连接发送HTTP请求服务器响应TCP链接断开渲染页面解析一 HTML解析过程解…

带你快速了解后端API服务的搭建

前言&#xff1a;写这篇文章的初衷是想分享一下我学习搭建后端API的过程&#xff0c;希望能帮助到和我一样想快速写API接口并部署到服务器上的同学&#x1f61c; 第一步&#xff1a;创建阿里云服务器 1、首先注册一个阿里云账号&#x1f917; 2、出于学习成本考虑&#xff0…

Linux系统,docker容器内查看pikachu源代码

在Linux系统中&#xff0c;要查看Docker容器内的Pikachu的源代码&#xff0c;需要先确保Pikachu的Docker镜像已经运行在系统上。以下是步骤和示例代码&#xff1a; 1、查找Pikachu容器的ID或名称&#xff1a; docker ps -a 2、使用docker exec命令进入运行中的Pikachu容器&am…

Spring MVC的运行流程详解

Spring MVC作为一个广泛使用的框架&#xff0c;提供了灵活且强大的MVC架构支持。尤其在业务系统中&#xff0c;Spring MVC能够有效地处理大量并发请求&#xff0c;提供良好的用户体验。本文将详细讲解Spring MVC的运行流程&#xff0c;以电商交易系统为案例&#xff0c;帮助读者…

不再烦恼!四款AI工具助你轻松打造完美PPT

嘿&#xff0c;各位办公室的小伙伴们&#xff0c;今儿咱们来聊聊那些让咱们工作生活大变样的“智能小伙伴”。作为每天跟PPT打交道的办公室文员&#xff0c;我敢说&#xff0c;自从有了这些神器&#xff0c;我的工作效率简直坐上了火箭&#xff0c;嗖嗖地往上涨&#xff01; 1…

Vue-Lecture1-Notes

渐进式框架 Vue 被称为“渐进式框架”&#xff0c;是因为它允许开发者根据项目的需求逐步引入和使用其功能&#xff0c;而不需要一次性使用整个框架。简单来说&#xff0c;Vue 提供了从简单到复杂的功能层次&#xff0c;可以灵活选择使用。 按需使用&#xff1a;Vue 的核心功能…

CSP-J Day 3 模拟赛补题报告

姓名&#xff1a;王胤皓&#xff0c;校区&#xff1a;和谐校区&#xff0c;考试时间&#xff1a; 2024 2024 2024 年 10 10 10 月 3 3 3 日 9 : 00 : 00 9:00:00 9:00:00~ 12 : 30 : 00 12:30:00 12:30:00&#xff0c;学号&#xff1a; S 07738 S07738 S07738 请关注作者的…

docker运行arm64架构的镜像、不同平台镜像构建

背景 Docker 允许开发者将应用及其依赖打包成一个轻量级、可移植的容器&#xff0c;实现“一次构建&#xff0c;到处运行”的目标。然而&#xff0c;不同的操作系统和硬件架构对容器镜像有不同的要求。例如&#xff0c;Linux 和 Windows 系统有不同的文件系统和系统调用&#…

银河麒麟桌面操作系统修改默认Shell为Bash

银河麒麟桌面操作系统修改默认Shell为Bash &#x1f490;The Begin&#x1f490;点点关注&#xff0c;收藏不迷路&#x1f490; 在银河麒麟桌面操作系统&#xff08;ARM版&#xff09;中&#xff0c;若要将默认Shell从Dash改为Bash&#xff0c;可执行以下步骤&#xff1a; 打开…