【Linux C | 多线程编程】线程的连接、分离,资源销毁情况

😁博客主页😁:🚀https://blog.csdn.net/wkd_007🚀
🤑博客内容🤑:🍭嵌入式开发、Linux、C语言、C++、数据结构、音视频🍭
⏰发布时间⏰:2024-04-01 14:52:46

本文未经允许,不得转发!!!

目录

  • 🎄一、概述
    • ✨1.1
    • ✨1.2
  • 🎄二、线程的连接 pthread_join
  • 🎄三、线程的分离 pthread_detach
  • 🎄四、必须连接线程 或 分离线程
  • 🎄五、总结


在这里插入图片描述

🎄一、概述

记住一句话,“创建线程后,要么连接该线程,要么使该线程分离,否则可能导致资源无法释放”。

怎样连接一个线程,连接线程是注意什么?
为什么要连接线程?
怎样分离一个线程,分离线程是注意什么?

本文将围绕线程的连接、分离操作去召开,让读者可以清楚上面几个问题的答案。


✨1.1

✨1.2

在这里插入图片描述

🎄二、线程的连接 pthread_join

线程连接(joining):使用 pthread_join 函数,用来等待某线程的退出并接收它的返回值。

线程连接可以用来等待一个线程执行,也可以用来获取线程的返回值,也可以即等待线程的结束又获取线程的返回值。这个等待有点类似于进程等待子进程退出的wait操作,但是有两点区别:

  • 1、进程间的等待只能是父进程等待子进程,而线程则没有这样的说法,只要是在一个线程组(进程)内,就可以对另外一个线程执行连接(join)操作;
  • 2、进程可以等待任一子进程的退出。而线程没有这样的操作,需要明确指定要连接的线程ID。这样的设计可以避免库函数的线程被连接了而导致库函数无法连接自己的线程。

pthread_join函数原型:

#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);
Compile and link with -pthread.
  • 函数描述:pthread_join可以等待某线程的退出并接收它的返回值。根据等待的线程是否退出, 可得到如下两种情况:
    • 等待的线程尚未退出, 那么pthread_join的调用线程就会陷入阻塞。
    • 等待的线程已经退出, 那么pthread_join函数会将线程的退出值(void*类型) 存放到retval指针指向的位置。
  • 函数参数:
    • thread:传入参数,要连接的线程ID;
    • retval:传出参数,用来接收线程返回值。
  • 函数返回值:成功返回 0,调用失败,和pthread_create函数一样, errno作为返回值返回。
    • EDEADLK:死锁,如自己连接自己,或者A连接B,B又连接A;
    • EINVAL:线程不是一个可连接(joinable)的线程;
    • EINVAL:已经有其他线程捷足先登,连接目标线程;
    • ESRCH:传入的线程ID不存在,查无此线程。

可能产生连接死锁的两个情况:
1、线程A连接线程A(自己连接自己);
2、线程A连接线程B,线程B连接线程A。
在这里插入图片描述

🌰举例子:

// 07_pthread_join.c
// gcc 07_pthread_join.c -l pthread
#include <stdio.h>
#include <pthread.h>int ret = -1;		// 全局变量记录线程返回值
void *func(void *arg)
{int *parg = arg;printf("this thread arg is %d, my threadID is %lx \n", *parg, (unsigned long)pthread_self());ret=*parg+1;return (void*)&ret;
}
int main()
{int arg=10;pthread_t threadId;pthread_create(&threadId, NULL, func, &arg);int *pRet = NULL;pthread_join(threadId, (void**)&pRet);printf("pthread_join end, ret=%d\n", *pRet);return 0;
}

在这里插入图片描述

🎄三、线程的分离 pthread_detach

默认情况下, 新创建的线程处于可连接(Joinable)的状态, 可连接状态的线程退出后, 需要对其执行连接操作, 否则线程资源无法释放,从而造成资源泄漏。

有时,我们并不关心该线程的返回值,也不想阻塞等待,但不执行连接又会资源泄露,那怎么办?线程库考虑到这种使用场景,提供了 pthread_detach 函数可将线程设置成已分离(detached)状态。处于已分离(detached)状态的线程退出时,由系统负责回收该线程的资源。

pthread_detach函数原型:

#include <pthread.h>
int pthread_detach(pthread_t thread);
Compile and link with -pthread.
  • 函数描述:pthread_detach函数将thread指定的线程标记为已分离。当一个分离的线程终止时,它的资源会自动释放回系统,而不需要另一个线程与终止的线程连接。试图分离已分离的线程会导致未指定的行为。
  • 函数参数:thread,要分离的线程ID。
  • 函数返回值:成功返回 0,调用失败,和pthread_create函数一样, errno作为返回值返回。
    • EINVAL:线程不是一个可连接(joinable)的线程,已经处于已分离状态;
    • ESRCH:传入的线程ID不存在,查无此线程。

其他相关内容:

  • 1、将线程的属性设定为已分离的第2种方式,使用 pthread_attr_setdetachstate 函数,这个比较少用可以了解一下。
    #include <pthread.h>
    int pthread_attr_setdetachstate(pthread_attr_t *attr,int detachstate);
    int pthread_attr_getdetachstate(pthread_attr_t *attr,int *detachstate);
    
  • 2、线程分离可由其他线程对其执行分离,也可以线程自己执行pthread_detach函数, 将自身设置成已分离的状态:
    pthread_detach(pthread_self());
    
  • 3、所谓已分离, 并不是指线程失去控制, 不归线程组管理, 而是指线程退出后, 系统会自动释放线程资源。

🌰举例子:下面例子是主线程执行分离的,更常见的是,线程内部自己执行分离。

// 07_pthread_detach.c
// gcc 07_pthread_detach.c -l pthread
#include <stdio.h>
#include <pthread.h>
void *func(void *arg)
{int *parg = arg;printf("this thread arg is %d, my threadID is %lx \n", *parg, (unsigned long)pthread_self());return NULL;
}
int main()
{int arg=10;pthread_t threadId;pthread_create(&threadId, NULL, func, &arg);pthread_detach(threadId);	// 对该线程执行分离printf("pthread_detach exec\n");while(1); // 保持主线程不退出return 0;
}

在这里插入图片描述

🎄四、必须连接线程 或 分离线程

这一小节,我们了解为什么必须要 连接线程分离线程 ?
因为既不分离线程又不连接已经退出的线程,可能会导致资源无法释放。

注意已连接已分离 的线程退出时,该线程的资源并没有立即调用munmap来释放掉,而是保留着被后面新建的线程复用。NPTL线程库的设计。
释放线程资源的时候,NPTL认为进程可能再次创建线程,而频繁地munmap和mmap会影响性能,所以NTPL将该栈缓存起来,放到一个链表之中,如果有新的创建线程的请求,NPTL会首先在栈缓存链表中寻找空间合适的栈,有的话,直接将该栈分配给新创建的线程。

下面通过一个例子来看看线程退出后的资源情况。

// 07_pthread_join_detach_test.c
// gcc 07_pthread_join_detach_test.c -l pthread -DNO_JOIN_DETACH	// 没有连接、分离
// gcc 07_pthread_join_detach_test.c -l pthread -DTHREAD_JOIN		// 使用线程的连接
// gcc 07_pthread_join_detach_test.c -l pthread -DTHREAD_DETACH		// 使用线程的分离
#include <stdio.h>
#include <pthread.h>
#include <sys/syscall.h>
void *func(void *arg)
{
#ifdef THREAD_DETACHpthread_detach(pthread_self());
#endifprintf("threadID = %lx, TID=%u \n", (unsigned long)pthread_self(), syscall(SYS_gettid));// 获取线程属性pthread_attr_t gattr;int s = pthread_getattr_np(pthread_self(), &gattr);if (s != 0){printf("pthread_getattr_np error\n");return NULL;}// 获取线程栈地址和大小void *stkaddr;size_t v;s = pthread_attr_getstack(&gattr, &stkaddr, &v);if (s != 0){printf("pthread_attr_getstackaddr error\n");return NULL;}printf("Stack address = %p, size=%luk btye\n", stkaddr, v/1024);sleep(3);printf("TID=%u EXIT\n", syscall(SYS_gettid));return NULL;
}
int main()
{
#ifdef NO_JOIN_DETACHprintf("NO_JOIN_DETACH PID=%u\n",syscall(SYS_gettid));
#endif
#ifdef THREAD_JOINprintf("THREAD_JOIN PID=%u\n",syscall(SYS_gettid));
#endif
#ifdef THREAD_DETACHprintf("THREAD_DETACH PID=%u\n",syscall(SYS_gettid));
#endif// 1、创建第一个线程pthread_t threadId, threadId2;pthread_create(&threadId, NULL, func, NULL);// 2、等待上个线程结束
#ifdef THREAD_JOINpthread_join(threadId, NULL);
#else // 没有等待线程,就使用sleep等待上个线程结束sleep(5);
#endif// 3、创建第二个线程pthread_create(&threadId2, NULL, func, NULL);pause(); // 保持主线程不退出return 0;
}

下面代码演示了线程资源使用的三个情况,下面分别看看其运行结果:

  • 🌰1、没有连接、分离;
    复制上面代码,运行gcc 07_pthread_join_detach_test.c -l pthread -DNO_JOIN_DETACH编译,可以看到各个线程的栈地址不一样:
    在这里插入图片描述
    运行pmap查看内存分布情况,也可以看到这两个地址,如下图:
    在这里插入图片描述
    可以得出一个结论:如果线程既不连接、又不分离的话,那么:
    1) 已经退出的线程,其空间没有被释放,仍然在进程的地址空间之内。
    2) 新创建的线程,没有复用刚才退出的线程的地址空间。

  • 🌰2、使用线程的连接;
    接下来看看,使用了线程连接的情况,上面代码运行gcc 07_pthread_join_detach_test.c -l pthread -DTHREAD_JOIN编译,运行结果如下,可以看到两个线程的栈地址是一样的,也就是说,第一个线程退出后,其地址空间被后面新建的线程复用
    在这里插入图片描述
    运行pmap查看进程内存分布情况,只有一个线程栈地址,如下图:
    在这里插入图片描述

  • 🌰3、使用线程的分离。
    接下来看看,使用了线程分离的情况,上面代码运行gcc 07_pthread_join_detach_test.c -l pthread -DTHREAD_DETACH编译,运行结果如下,可以看到两个线程的栈地址是一样的,也就是说,第一个线程退出后,其地址空间被后面新建的线程复用
    在这里插入图片描述
    运行pmap查看进程内存分布情况,只有一个线程栈地址,如下图:
    在这里插入图片描述


在这里插入图片描述

🎄五、总结

本文结束了Linux系统编程的线程的连接(pthread_join)、线程的分离(pthread_detach),以及介绍了为什么要使用线程的连接、分离。

在这里插入图片描述
如果文章有帮助的话,点赞👍、收藏⭐,支持一波,谢谢 😁😁😁

参考资料:
《Linux环境编程:从应用到内核》

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

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

相关文章

SSM框架学习——MyBatis关联映射

MyBatis关联映射 为什么要关联映射 实际开发中&#xff0c;对数据库操作常常会涉及多张表&#xff0c;所以在OOP中就涉及对象与对象的关联关系。针对多表操作&#xff0c;MyBatis提供关联映射。 关联关系概述 一对一&#xff1a;A类中定义B类的属性b&#xff0c;B类中定义A…

MCU友好过渡MPU,米尔基于STM32MP135开发板裸机开发应用笔记

以前微处理器&#xff08;MPU&#xff09;与微控制器&#xff08;MCU&#xff09;是截然不同的两种设备&#xff0c;MPU支持丰富的软件系统&#xff0c;如Linux和相关的软件堆栈&#xff0c;而MCU通常将专注于裸机和RTOS。近年来&#xff0c;随着MCU的性能越来越高&#xff0c;…

【Spring源码】WebSocket做推送动作的底层实例

一、前瞻 Ok&#xff0c;开始我们今天的对Spring的【模块阅读】。 那就挑Web里的WebSocket模块&#xff0c;先思考下本次阅读的阅读线索&#xff1a; WebSocket在Spring里起到什么作用这个模块采用了什么设计模式我们都知道WebSocket可以主动推送消息给用户&#xff0c;那做推…

halcon图像膨胀

1、原理&#xff1a; 使用结构元素在图像上移动&#xff0c;如果结构元素中有任意一个像素和图像上的非零像素重叠&#xff0c;则保留此时结构元素中心所在位置&#xff0c;并将其像素值设置为非零。 2、halcon代码 其中圆形结构元素可设置半径&#xff0c;矩形结构元素设置…

MySQL故障排查与生产环境优化

一、MySQL单实例常见故障 1.逻辑架构图 MySQL逻辑架构图客户端和连接服务核心服务功能存储引擎层数据存储层 2.故障一 故障现象 ERROR 2002 (HY000): Cant connect to local MySQL server through socket/data/mysql/mysql.sock(2) 问题分析 数据库未启动或者数据库端口…

Yolo 自制数据集dect训练改进

上一文请看 Yolo自制detect训练-CSDN博客 简介 如下图&#xff1a; 首先看一下每个图的含义 loss loss分为cls_loss, box_loss, obj_loss三部分。 cls_loss用于监督类别分类&#xff0c;计算锚框与对应的标定分类是否正确。 box_loss用于监督检测框的回归&#xff0c;预测框…

蓝桥杯真题:成绩统计

这题思路简单&#xff0c;但是输出结果的位置容易出错&#xff0c;题目要求四舍五入&#xff0c;所以要用Math.round&#xff08;&#xff09;的方法

Python快速入门系列-7(Python Web开发与框架介绍)

第七章:Python Web开发与框架介绍 7.1 Flask与Django简介7.1.1 Flask框架Flask的特点Flask的安装一个简单的Flask应用示例7.1.2 Django框架Django的特点Django的安装一个简单的Django应用示例7.2 前后端交互与数据传输7.2.1 前后端交互7.2.2 数据传输格式7.2.3 示例:使用Flas…

解决GNU Radio+USRP实现OFDM收发在接收端存在误码问题

文章目录 前言一、OFDM 收发流程1、OFDM 收端流程2、OFDM 收端流程 二、问题所在1、find_trigger_signal 函数解读2、general_work 函数3、问题所在 三、修改源码四、运行结果1、频谱2、传输数据测试 五、调试小技巧六、资源自取 前言 在使用 GNU Radio 时使用官方例程搭建 GN…

git clone没有权限的解决方法

一般情况 git clone时没有权限&#xff0c;一般是因为在代码库平台上没有配置本地电脑的id_rsa.pub 只要配置上&#xff0c;一般就可以正常下载了。 非一般情况 但是也有即使配置了id_rsa.pub后&#xff0c;仍然无法clone代码的情况。如下 原因 这种情况是因为ssh客户端…

前端常用代码整理— js,jquery篇(3)

目录 1.判断是否是json字符串 2.获取当前网址 3.将文本复制到剪贴板 4.获取一个月的天数 5.展平数组 6.要修改getRandomItem函数以返回数组中的随机两个元素&#xff0c;可以尝试以下代码 1.判断是否是json字符串 const isJson str > {try {JSON.parse(str);return …

通过 Cookie、Session 和 Spring 拦截器技术,实现对用户登录状态的持有和清理(一)

本篇博客对应“2.3 会话管理”小节 视频名称&#xff1a;会话管理 视频链接 什么是HTPP协议&#xff1f; HTTP&#xff0c;Hpyer Text Transfer Protocl&#xff1a;定义了浏览器怎样从&#xff08;万维网客户进程&#xff09;怎样向Web服务器&#xff08;万维网服务器&#…

vue 文件下载

1.返回路径下载 注: 针对一些浏览器无法识别的文件格式&#xff08;如pdf、xls、ppt&#xff09;。可以直接在地址栏上输入URL即可触发浏览器的下载功能。 情况1 //地址栏输入文件URLwindow.location.href URLwindow.open(URL) 注:该方式将下载逻辑放在后端处理&#xff0c…

Mysql的高级语句3

目录 一、子查询 注意&#xff1a;子语句可以与主语句所查询的表相同&#xff0c;但是也可以是不同表。 1、select in 1.1 相同表查询 1.2 多表查询 2、not in 取反&#xff0c;就是将子查询结果&#xff0c;进行取反处理 3、insert into in 4、update…

IO练习题

1&#xff1a;使用 dup2 实现错误日志功能 使用 write 和 read 实现文件的拷贝功能&#xff0c;注意&#xff0c;代码中所有函数后面&#xff0c;紧跟perror输出错误信息&#xff0c;要求这些错误信息重定向到错误日志 err.txt 中 #include <myhead.h>//文件IO实现文件拷…

实现offsetof宏以及交换一个整数二进制奇偶位的宏

目录 1. offsetof宏2. 交换奇偶位 1. offsetof宏 我们想用宏来实现offsetof函数,首先要了解这个函数的用法。 1.1 offsetof函数的介绍及用法 &#xff08;1&#xff09;功能&#xff1a;用来计算结构体中一个成员在该结构体中的相对起始位置的偏移量&#xff0c;单位是字节。 …

ClamAV:Linux服务器杀毒扫描工具

Clam AntiVirus&#xff08;ClamAV&#xff09;是免费而且开放源代码的防毒软件&#xff0c;软件与病毒码的更新皆由社群免费发布。ClamAV在命令行下运行&#xff0c;它不将杀毒作为主要功能&#xff0c;默认只能查出系统内的病毒&#xff0c;但是无法清除。需要用户自行对病毒…

C语言之位段

1.位段的声明 位段的声明和结构是类似的&#xff0c;有两个不同&#xff1a; 1.位段的成员必须是 int、unsigned int 或signed int 。 2.位段的成员名后边有一个冒号和一个数字。 比如&#xff1a; struct A {int _a:2;int _b:5;int _c:10;int _d:30; }; A 就是一个位段类型…

【上海大学计算机组成原理实验报告】二、数据传送实验

一、实验目的 了解在模型机中算术、逻辑运算单元的控制方法。学习机器语言程序的运行过程。通过人工译码&#xff0c;加深对译码器基本工作原理的理解。 二、实验原理 根据实验指导书的相关内容&#xff0c;本次实验所要用的CP226实验仪在手动方式下&#xff0c;运算功能通过…

LeetCode-热题100:160. 相交链表

给你两个单链表的头节点 headA 和 headB &#xff0c;请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点&#xff0c;返回 null 。 图示两个链表在节点 c1 开始相交&#xff1a; 题目数据 保证 整个链式结构中不存在环。 注意&#xff0c;函数返回结果后&…