Linux学习日记18:线程的分离

一、前言

前面我们学习了线程的一些基础知识,学习了线程的创建与使用,今天我们来学习线程的分离与同步。

二、线程分离

2.1、函数原型

函数原型如下:

cs 复制代码
#include <pthread.h>
int pthread_detach(pthread_t thread);

参数:thread:设置为分离态的线程 ID(用户态 TID,由 pthread_create() 返回的 ID)。

返回值:成功:返回0;失败:返回对应的错误码。

2.2、工作原理

pthread_detach()本质是**修改线程的分离状态属性,**通知内核:该线程终止时,无需保留资源等待pthread_join(),自动回收资源;具体执行流程如下:

1、**用户态方面:**pthread库修改该线程对应的属性结构体(pthread_attr_t)中detachstate字段为PTHREAD_CREATE_DETACHED;

2、内核态层面: 库通过系统调用通知内核,标记该线程的 task_struct(任务结构体)为 "分离态";

3、**线程终止时:**内核检测到分离态标记,会立即清理线程的栈、寄存器、TID 等资源,无需主线程干预。

注:分离操作不影响线程的执行 ,仅改变线程终止后的资源回收规则

2.3、典型示例

1、先创建再detach

先创建一个pthread_detach.c文件,然后输入以下代码:

cs 复制代码
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <string.h>

void *myfun(void *arg)
{
        printf("child pthread id is %ld\n",pthread_self());
        for(int i=0;i<5;i++)
        {
                printf("children i =%d\n",i);
                sleep(1);
        }
        return NULL;//子线程结束,自动回收资源并退出
}
int main()
{
        pthread_t pthid;
        int ret;
        ret = pthread_create(&pthid,NULL,myfun,NULL);//线程创建
        pthread_detach(pthid);//线程分离
        if(ret != 0)
        {
                printf("error number is %d\n",ret);
                printf("%s\n",strerror(ret));
        }
        printf("parent pthread id is %ld\n",pthread_self());
        for(int i=0;i<5;i++)
        {
                printf("parent i = %d\n",i);
                sleep(1);
        }
        sleep(2);//休眠2s,防止主线程快速退出导致进程退出
        return 0;
}

使用gcc编译器进行编译,运行结果如下:

可以看到,主线程并不会等待子进程,子进程在后台跑,子线程自动释放资源,这就是线程分离的目的。

2、创建时直接设置为分离态

有时不想手动设置分离态,可以让线程创建时直接分离,具体代码如下:

cs 复制代码
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <string.h>

void *myfun(void *arg)
{
        printf("child pthread id is %ld\n",pthread_self());
        for(int i=0;i<5;i++)
        {
                printf("children i =%d\n",i);
                sleep(1);
        }
        return 0;
}
int main()
{
        pthread_t pthid;
        pthread_attr_t attr;
        //init
        pthread_attr_init(&attr);//初始化线程属性对象
        //set
        pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);//设置线程为分离态
        //set2
        int ret;
        ret = pthread_create(&pthid,&attr,myfun,NULL);//创建线程,并使其天生就是分离状态
        pthread_detach(pthid);
        if(ret != 0)
        {
                printf("error number is %d\n",ret);
                printf("%s\n",strerror(ret));
        }
        printf("parent pthread id is %ld\n",pthread_self());
        for(int i=0;i<5;i++)
        {
                printf("parent i = %d\n",i);
                sleep(1);
        }
        sleep(2);
        //kill attr
        pthread_attr_destory(&attr);//销毁属性对象(不影响已经创建的线程)
        return 0;
}

使用gcc编译器进行编译,运行结果如下:

运行结果与第一种方法一样,但是这是创建时直接分离,避免了线程创建失败而导致后续分离操作无效的情况。

2.4、注意事项

1、分离状态不可逆: 线程一旦通过 pthread_detach() 设为分离态,无法再改回 joinable状态。如果后续试图调用 pthread_join(),会直接返回 EINVAL 错误。

2、调用时机越早越好,避免线程提前终止:若主线程创建子线程后,还没来得及调用 pthread_detach(),子线程就已经终止,此时调用 pthread_detach() 会返回 ESRCH(找不到线程);

3、不能对已终止的线程调用pthread_detach():线程终止后,其 TID 可能被内核复用(分配给新创建的线程),此时调用 pthread_detach() 可能误操作新线程,导致不可预期的问题。

4、分离线程崩溃仍会导致进程终止:pthread_detach() 仅改变资源回收方式,不改变线程与进程的资源共享关系:如果分离线程触发段错误、除零等异常,仍会发送信号终止整个进程。

5、主线程提前退出仍会杀死分离线程:分离线程只是 "终止后自动回收资源",但如果主线程直接return/exit导致进程终止,所有分离线程仍会被强制杀死(需主线程 pthread_exit()或延时等待)。

6、不能同时调用 pthread_join() 和 pthread_detach():若先调用 pthread_join()阻塞等待线程,此时再调用 pthread_detach() 会返回 EINVAL;若先调用 pthread_detach() 分离线程,再调用 pthread_join()也会返回 EINVAL;

7、主线程无需分离(无意义):主线程的 TID 等于进程 PID,分离主线程不会改变 "主线程退出导致进程终止" 的规则,因此对主线程调用 pthread_detach()无实际意义,还可能返回 EINVAL 错误。

2.5、两种创建方法对比

二者对比如下:

特性 pthread_detach()(动态分离) pthread_attr_setdetachstate()(静态创建)
调用时机 线程创建后任意时间(需线程未终止) 线程创建前(初始化属性时)
灵活性 高(运行时决定是否分离) 低(创建前确定,无法动态调整)
代码复杂度 低(无需初始化属性结构体) 高(需初始化 / 销毁属性结构体)
风险 可能因线程提前终止导致调用失败 无此风险(创建时直接设为分离态)
适用场景 运行时才确定是否分离的场景 提前确定无需等待的后台线程
相关推荐
科技林总7 小时前
【系统分析师】1.1 信息与信息系统
学习
HyperAI超神经12 小时前
在线教程丨 David Baker 团队开源 RFdiffusion3,实现全原子蛋白质设计的生成式突破
人工智能·深度学习·学习·机器学习·ai·cpu·gpu
Elastic 中国社区官方博客14 小时前
使用 Elastic Cloud Serverless 扩展批量索引
大数据·运维·数据库·elasticsearch·搜索引擎·云原生·serverless
超龄超能程序猿15 小时前
Docker GPU插件(NVIDIA Container Toolkit)安装
运维·docker·容器
YJlio15 小时前
VolumeID 学习笔记(13.10):卷序列号修改与资产标识管理实战
windows·笔记·学习
小龙15 小时前
【学习笔记】多标签交叉熵损失的原理
笔记·学习·多标签交叉熵损失
Dillon Dong15 小时前
从C到Simulink: 使用STM32硬件支持包后为什么还不支持PC仿真ARM建模程序
c语言·stm32·simulink
Xの哲學16 小时前
Linux SMP 实现机制深度剖析
linux·服务器·网络·算法·边缘计算
2501_9061505616 小时前
私有部署问卷系统操作实战记录-DWSurvey
java·运维·服务器·spring·开源
知识分享小能手16 小时前
Ubuntu入门学习教程,从入门到精通,Ubuntu 22.04的Linux网络配置(14)
linux·学习·ubuntu