【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 让系统自动回收(非阻塞)。二选一,绝不遗漏,否则僵尸线程会吃光你的内存!

相关推荐
HLC++12 小时前
Linux的进程间通信
android·linux·服务器
华清远见IT开放实验室13 小时前
实验室建设案例 | 石家庄科技信息职业学院嵌入式实验室——从底层硬件到系统应用,一所应用型高校的嵌入式人才培养这样落地
linux·arm开发·stm32·嵌入式硬件·高校·实验室建设
groundhappy15 小时前
idalib安装和codex ida-mcp配置
linux·开发语言·python
通信小小昕16 小时前
Ubuntu 26.04 中文输入法安装
linux·运维·ubuntu
张小姐的猫17 小时前
【Linux】网络编程 —— HTTP协议(上)
linux·运维·服务器·网络·http·单例模式·策略模式
栩栩云生18 小时前
AI 写代码犯的错,早被写进了错题集
linux·安全·ai编程
imc.1119 小时前
linux基础IO
linux·运维·服务器
BelongPanda21 小时前
Linux Nginx 纯手动 Let‘s Encrypt 泛域名证书配置教程
linux·nginx
酷可达拉斯21 小时前
Linux操作系统-shell编程(0)
linux·运维·服务器·python·云计算
2301_7779983421 小时前
Linux中断机制:操作系统如何高效运行
linux·运维·服务器