【Linux开发】04Linux 线程的销毁

一、问题:线程结束就自动销毁了吗?

1.1 一个容易被忽略的事实

很多初学者认为:线程函数执行完 return 后,线程就"消失"了,所有资源都被释放。这是错误的!

实际上,Linux 线程结束后,它的线程状态部分资源 (如线程栈)并不会自动释放,而是处于一种类似"僵尸线程"的状态。如果不主动回收,这些资源会一直占用内存,导致资源泄漏

1.2 类比:进程需要 wait,线程也需要 join

进程 线程
子进程结束变成僵尸进程 线程结束变成僵尸线程
父进程需要 wait() 回收 主线程需要 pthread_join() 回收
或者设置 signal(SIGCHLD, SIG_IGN) 自动回收 或者调用 pthread_detach() 自动回收

核心结论:线程结束 ≠ 线程资源被释放,必须主动回收!


二、线程销毁的两种方法

2.1 方法一:pthread_join(阻塞式等待)

c 复制代码
int pthread_join(pthread_t thread, void **retval);
  • 作用:等待指定线程结束,并回收其资源。
  • 特点 :调用线程会阻塞,直到目标线程终止。
  • 优点:可以获取线程的返回值。
  • 缺点:如果线程一直不结束,调用者会一直等。

适用场景:需要知道线程何时结束,或需要获取返回值时。

2.2 方法二:pthread_detach(非阻塞分离)

c 复制代码
int pthread_detach(pthread_t thread);
  • 作用 :将线程标记为"分离状态",线程结束后自动回收资源
  • 特点 :调用后立即返回,不阻塞
  • 优点 :不需要 pthread_join,线程结束自动清理。
  • 缺点 :无法获取线程的返回值,也不能再 join

适用场景:不需要关心线程何时结束,也不需要返回值的"后台任务"。


三、代码示例

3.1 使用 pthread_join

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

void* thread_func(void* arg) {
    printf("子线程:开始工作...\n");
    sleep(2);
    printf("子线程:工作完成\n");
    return (void*)42;   // 返回一个值
}

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

    // 创建线程
    pthread_create(&tid, NULL, thread_func, NULL);

    // 等待线程结束,并获取返回值
    pthread_join(tid, &ret);

    printf("主线程:子线程返回值 = %ld\n", (long)ret);
    return 0;
}

输出

复制代码
子线程:开始工作...
子线程:工作完成
主线程:子线程返回值 = 42

说明

  • 主线程在 pthread_join 处阻塞,直到子线程结束。
  • 子线程的返回值通过 ret 获取。
  • 子线程资源被自动回收。

3.2 使用 pthread_detach

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

void* thread_func(void* arg) {
    // 分离自身(常用写法)
    pthread_detach(pthread_self());

    printf("子线程:开始工作...\n");
    sleep(2);
    printf("子线程:工作完成,自动销毁\n");
    return NULL;
}

int main() {
    pthread_t tid;

    pthread_create(&tid, NULL, thread_func, NULL);

    // 主线程继续做其他事情,不等子线程
    printf("主线程:继续执行...\n");
    sleep(3);   // 等待子线程完成(否则主线程结束进程会终止子线程)

    printf("主线程:结束\n");
    return 0;
}

输出

复制代码
主线程:继续执行...
子线程:开始工作...
子线程:工作完成,自动销毁
主线程:结束

说明

  • 子线程调用 pthread_detach(pthread_self()) 将自己设为分离状态。
  • 主线程不需要 pthread_join,子线程结束后资源自动回收。
  • 主线程必须"活得够久",否则进程结束会强制终止所有线程。

3.3 在创建时直接设置分离属性

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

void* thread_func(void* arg) {
    printf("子线程:执行中...\n");
    sleep(1);
    return NULL;
}

int main() {
    pthread_t tid;
    pthread_attr_t attr;

    // 1. 初始化线程属性
    pthread_attr_init(&attr);
    // 2. 设置分离状态
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
    // 3. 创建分离线程
    pthread_create(&tid, &attr, thread_func, NULL);
    // 4. 销毁属性对象(不再需要)
    pthread_attr_destroy(&attr);

    printf("主线程:继续执行...\n");
    sleep(2);   // 等子线程完成
    return 0;
}

说明 :这种方式更优雅,子线程从一开始就是分离的,无需在函数内部调用 pthread_detach


四、常见错误与注意事项

4.1 既 join 又 detach

c 复制代码
pthread_detach(tid);   // 设为分离
pthread_join(tid, NULL); // 错误!分离线程不能再 join

分离线程不能再被 join,否则 pthread_join 会返回错误(通常为 EINVAL)。

4.2 主线程提前退出

c 复制代码
int main() {
    pthread_t tid;
    pthread_create(&tid, NULL, thread_func, NULL);
    // 没有 join,也没有 sleep
    return 0;   // 进程结束,所有线程被强制终止
}

后果 :子线程可能还没执行完就被强行终止。解决方法是调用 pthread_join 等待,或让主线程 sleep 足够长时间(不推荐)。

4.3 忘记回收导致内存泄漏

c 复制代码
void* worker(void* arg) {
    // 工作...
    return NULL;
}

int main() {
    while (1) {
        pthread_t tid;
        pthread_create(&tid, NULL, worker, NULL);
        // 没有 join,也没有 detach
        // 每次循环都会泄漏一个线程的资源
    }
}

后果 :长时间运行后,内存被僵尸线程耗尽。必须使用 pthread_joinpthread_detach

4.4 分离后仍想同步

分离线程无法被 join,如果主线程需要知道子线程的完成时机,就不能分离。可以使用条件变量全局标志来同步,但会增加复杂度。


五、两种方法的对比总结

特性 pthread_join pthread_detach
是否阻塞 ✅ 阻塞 ❌ 不阻塞
获取返回值 ✅ 可以 ❌ 不可以
能否再次回收 只能一次 分离后不能再操作
适用场景 需要等待结果 后台任务,无需结果
资源回收 调用时回收 线程结束时自动回收
主线程需等待 自动同步 需额外同步(如 sleep)

六、记忆口诀

复制代码
线程结束不销毁,就像僵尸满地跑。
主动回收有两种:join 和 detach。

join 阻塞等结果,返回值也能拿到。
detach 分离不阻塞,自动回收省烦恼。

分离不能再 join,主线程别先溜掉。
资源回收要记牢,内存泄漏不能要。

七、一句话总结

线程结束 ≠ 资源释放。要么用 pthread_join 主动等待回收(阻塞),要么用 pthread_detach 让系统自动回收(非阻塞)。二选一,绝不遗漏,否则僵尸线程会吃光你的内存!

相关推荐
HenryLiuu2 小时前
Ubuntu 20.04, cuda 12.1版本安装flash attention 2教程
linux·ubuntu
Boop_wu10 小时前
[Java 算法] 字符串
linux·运维·服务器·数据结构·算法·leetcode
SkyXZ~12 小时前
Jetson有Jtop,Linux有Htop,RDK也有Dtop!
linux·运维·服务器·rdkx5·rdks100·dtop
starvapour13 小时前
Ubuntu系统下基于终端的音频相关命令
linux·ubuntu·音视频
杨云龙UP14 小时前
Oracle Data Pump实战:expdp/impdp常用参数与导入导出命令整理_20260406
linux·运维·服务器·数据库·oracle
浪客灿心14 小时前
线程同步与互斥
linux
牛马鸡niumasi16 小时前
C/C++ 程序编译过程、静态/动态链接、静态/动态库
linux
捧月华如17 小时前
Linux 系统性能压测工具全景指南(含工程实战)
linux·运维·服务器
YMWM_17 小时前
export MPLBACKEND=Agg命令使用
linux·python