4. 线程的生命周期管理
线程的生命周期,说白了就是一条命从生到死的过程。我做了这么多年系统编程,见过太多因为生命周期管理不当导致的诡异bug。今天咱们就把这事彻底聊透。
4.1 线程的五种状态与转换图
一个线程在它的生命周期里,会经历五种核心状态。我习惯用一张图来理解它们的关系:
这张图我画了很多遍,每次给团队新人讲线程管理都会拿出来。你仔细看,核心就三条线:
- 新建 → 就绪:线程创建后不会立刻跑,得等调度器点头
- 就绪 ↔ 运行:这是调度器的主战场,来回切换
- 运行 → 阻塞/等待 → 就绪:遇到I/O或同步操作,线程就得让出CPU
4.2 用ps和top命令查看线程状态
光讲理论不行,得能动手看。我平时排查问题,最常用的就是这两个命令。
4.2.1 ps命令查看线程
加个 -T 参数就能看到线程了:
# 查看进程1234的所有线程
ps -T -p 1234
# 更详细的输出,显示状态
ps -eLf | grep myapp
输出里有个 STAT 列,那玩意就是线程状态。常见的状态码:
| 状态码 | 含义 | 对应生命周期 |
|---|---|---|
| R | Running/Runnable | 运行或就绪 |
| S | Sleeping(可中断) | 阻塞 |
| D | Uninterruptible Sleep | 阻塞(不可中断) |
| T | Stopped | 暂停 |
| Z | Zombie | 僵尸 |
我的小技巧:看到大量 D 状态的线程,八成是磁盘I/O出问题了。我曾经在生产环境遇到过,一堆线程卡在 D 状态,最后发现是存储阵列挂了。
4.2.2 top命令查看线程
top 默认只看进程,想看线程得加 -H:
# 查看所有线程
top -H
# 只看某个进程的线程
top -H -p 1234
top 里有个 S 列,和 ps 的 STAT 是一个意思。我一般这么用:
- 按
P键按CPU排序,找最耗CPU的线程 - 按
M键按内存排序,找内存大户 - 看
S列,如果全是 R 但CPU不高,可能是死循环
注意:top 看到的线程ID(TID)和 ps 里的 LWP 是一个东西。别和进程ID搞混了,我见过有人拿TID当PID去kill,结果把整个进程干掉了。
4.3 僵尸线程与孤儿线程
这两个概念,说白了都是线程退出后的遗留问题。处理不好,系统资源就泄漏了。
4.3.1 僵尸线程
线程执行完了,但它的退出状态还没被回收。这时候线程就变成了僵尸。你看 ps 输出,状态是 Z。
为什么会这样?因为线程退出后,内核还保留着它的退出码、资源使用统计等信息。得有人来取走这些信息,线程才能真正消亡。
处理方式就一个:pthread_join。
#include <pthread.h>
#include <stdio.h>
void* worker(void* arg) {
printf("线程工作中...\n");
return (void*)42; // 退出码
}
int main() {
pthread_t tid;
pthread_create(&tid, NULL, worker, NULL);
// 关键:等待线程结束,回收资源
void* retval;
pthread_join(tid, &retval);
printf("线程退出码: %ld\n", (long)retval);
return 0;
}
避坑指南:我曾经写过一个服务,创建了大量线程但忘了join。结果跑了一周后,ps一看全是Z状态。系统负载不高,但线程数暴涨,最后把进程数上限打满了。从那以后,我每个pthread_create后面都紧跟着pthread_join,或者用分离线程。
4.3.2 孤儿线程
主线程退出了,但子线程还在跑。这时候子线程就成了孤儿。在Linux下,孤儿线程会被init进程(PID=1)收养。
处理方式有两种:
- 分离线程:用
pthread_detach让线程自己玩,退出后自动回收 - 等待所有线程:主线程退出前,join所有子线程
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
void* detached_worker(void* arg) {
printf("分离线程开始工作...\n");
sleep(2);
printf("分离线程结束\n");
return NULL;
}
int main() {
pthread_t tid;
pthread_create(&tid, NULL, detached_worker, NULL);
// 设置为分离状态
pthread_detach(tid);
// 主线程可以安全退出了
printf("主线程退出\n");
pthread_exit(NULL); // 注意:用exit()会干掉所有线程
}
重要:主线程退出时,如果用 exit() 或从 main 里 return,整个进程就结束了,所有线程都会被强制终止。想只退出主线程,用 pthread_exit()。
4.4 实际项目中的经验总结
说了这么多,我总结几条实战经验:
- 线程池是王道:别频繁创建销毁线程,用线程池管理。我见过一个项目每秒创建1000个线程,系统直接崩了。
- 分离还是join:如果不需要线程的返回值,就用分离线程。需要返回值,必须join。
- 监控线程状态:写个脚本定期ps -eLf,统计各状态线程数。一旦Z状态超过阈值,立刻报警。
- 资源清理:线程退出前,确保释放了所有资源。锁、内存、文件描述符,一个都不能漏。
嗯,线程生命周期这块,说白了就是管好线程的出生、活着、死亡三个阶段。出生要优雅,活着要监控,死亡要善后。做到这三点,线程管理就稳了。
可可精品资料库