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

相关推荐
AlfredZhao2 天前
生产环境里,为什么不建议把普通端口直接暴露到公网?
linux·https·443·80
戴为沐3 天前
Linux内存扩容指南
linux
zylyehuo3 天前
Linux 彻底且安全地删除文件
linux
用户805533698034 天前
主线 U-Boot 上 RK3506:和闭源 rkbin 拔河的三个隐性契约
linux·嵌入式
用户034095297914 天前
linux fcitx 5 雾凇拼音 设置在中文输入法下仍然输入英文标点
linux
Web3探索者6 天前
可视化服务器管理和传统命令行区别是什么?新手教程:Linux 运维到底该用图形界面还是 SSH 命令行?
linux·ssh
zylyehuo6 天前
Linux系统中网线与USB网络共享冲突
linux
Sokach10157 天前
Linux Shell 脚本从零到能用:一个新手的一天学习总结
linux
AlfredZhao8 天前
Docker 容器时区不对,`timedatectl` 不存在怎么办?
linux·timezone
zzzzzz3109 天前
9K Star 炸裂开源!这个 C 语言写的代码知识图谱,把 Linux 内核索引压缩到了 3 分钟
linux·服务器·sql