在嵌入式Linux开发中,你是否遇到过这样的场景:系统运行一段时间后,CPU使用率莫名飙升,使用 top 命令一看,发现是某个进程占了大头。但这还没完,这个进程内部可能运行着十几个甚至更多线程,仅仅知道是哪个进程还不够,我们必须进一步定位到具体是哪个“问题线程”在消耗资源。
今天我们就来探讨几种实用的方法,帮助你精确查看一个进程中各个线程的资源占用情况。
先准备一个多线程示例程序
为了清晰地演示,我们先编写一个简单的多线程C程序。这里采用了表驱动的方式来管理线程,这在复杂的实际项目中是一种清晰、可维护的做法。
multi_thread.c:
#define _GNU_SOURCE
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#define APP_THREAD_NAME_MAX_LEN 16
typedef enum _app_thread_index
{
APP_THREAD_INDEX_TEST0,
APP_THREAD_INDEX_TEST1,
APP_THREAD_INDEX_TEST2,
APP_THREAD_INDEX_TEST3,
APP_THREAD_INDEX_TEST4,
APP_THREAD_INDEX_TEST5,
APP_THREAD_INDEX_MAX
}app_thread_index_e;
typedef struct _app_thread
{
pthread_t thread_handle;
char name[APP_THREAD_NAME_MAX_LEN];
}app_thread_s;
app_thread_s s_app_thread_table[APP_THREAD_INDEX_MAX] =
{
{0, “test0_thread”},
{0, “test1_thread”},
{0, “test2_thread”},
{0, “test3_thread”},
{0, “test4_thread”},
{0, “test5_thread”}
};
static void *common_thread_entry(void *param)
{
app_thread_s *self = (app_thread_s *)param;
printf(“%s running…\n”, self->name);
while (1)
{
usleep(2 * 1000);
}
return NULL;
}
static int create_all_app_thread(void)
{
int ret = 0;
for (int i = 0; i < APP_THREAD_INDEX_MAX; i++)
{
ret = pthread_create(&s_app_thread_table[i].thread_handle, NULL,
common_thread_entry, &s_app_thread_table[i]);
if (0 != ret)
{
printf(“%s create error!\n”, s_app_thread_table[i].name);
return ret;
}
printf(“%s create success!\n”, s_app_thread_table[i].name);
pthread_setname_np(s_app_thread_table[i].thread_handle,
s_app_thread_table[i].name);
pthread_detach(s_app_thread_table[i].thread_handle);
}
return ret;
}
int main(int argc, char **argv)
{
create_all_app_thread();
while (1)
{
usleep(2 * 1000);
}
return 0;
}
编译并以后台方式运行:
gcc multi_thread.c -o multi_thread -lpthread
./multi_thread &
程序运行后,我们就可以使用下面几种方法来监控其内部线程的资源消耗了。
方法一:top -H(最常用)
top 命令大家都很熟悉,加上 -H 参数后,它就会将进程内的线程展开显示。如果再配合 -p 参数指定进程ID,就能只关注我们感兴趣的那个进程。
top -H -p `pidof multi_thread`
注意:命令中 pidof 两边使用的是反引号 `(位于键盘左上角ESC键下方),其作用是执行命令替换,将 pidof multi_thread 输出的进程ID作为 -p 的参数。
运行效果如下图所示,它清晰地展示了进程内每个线程的实时状态。

我们来解读一下输出中几个关键列:
- PID:此处显示的是线程ID(在Linux中也称为LWP,轻量级进程ID),而非进程ID。
- %CPU:该线程实时的CPU占用率。排查CPU高占用问题时,这是你需要重点关注的列。
- TIME+:线程累计使用的CPU时间。长时间运行的线程,这个值会比较大。
- COMMAND:线程名称。这正是我们通过
pthread_setname_np 函数设置的,是快速识别线程的关键。
这是日常调试中最直观、最常用的方法。
方法二:ps -T 快照查看
与 top 的持续刷新不同,ps 命令提供的是某个时间点的系统进程快照,更适合在脚本中使用或进行一次性查看。
ps -T -p `pidof multi_thread`
-T 参数用于显示指定进程下的所有线程。输出中的 SPID 列即为各线程的LWP。
如果你想获取更聚焦的信息,可以自定义输出列:
ps -T -p `pidof multi_thread` -o spid,comm,%cpu,time
这条命令一次性列出了线程ID、线程名、CPU占用率和累计CPU时间,非常方便。
方法三:pidstat 按线程采样
pidstat 是 sysstat 工具包中的一个命令,专为性能分析设计。它可以按固定时间间隔对线程的CPU使用率进行采样,输出格式规整且自带时间戳。
pidstat -t -p `pidof multi_thread` 1
其中 -t 参数表示显示线程级别的信息,末尾的 1 代表每隔1秒采样一次。
这个方法非常适合用来分析趋势或定位间歇性的CPU使用率“毛刺”。比如,你怀疑某个线程会周期性飙升CPU,用 pidstat 持续采集几分钟的数据,就很容易在输出中发现规律。
方法四:直接读取 /proc 文件系统
前面介绍的命令工具,其数据源头都是Linux的 /proc 虚拟文件系统。在极端情况下(例如系统极度精简,未安装 top、ps 等工具),直接读取 /proc 是最终的兜底方案。
一个进程的所有线程信息都位于 /proc/<pid>/task/ 目录下,每个线程对应一个以其线程ID(TID)命名的子目录。
ls /proc/`pidof multi_thread`/task/
进入任意一个线程目录,有几个重要的文件:
# 查看线程名
cat /proc/<pid>/task/<tid>/comm
# 查看线程详细状态(包含运行状态、优先级、内存使用等)
cat /proc/<pid>/task/<tid>/status
# 查看线程的CPU时间统计(关注utime用户态时间和stime内核态时间)
cat /proc/<pid>/task/<tid>/stat
虽然看起来比较原始,但通过编写简单的Shell脚本定时抓取这些信息,你就能在资源受限的嵌入式环境中搭建一个基础的线程监控机制。深入了解这些底层接口,对网络/系统层面的问题排查大有裨益。
关于线程命名的重要提示
在我们提供的示例C语言代码中,使用了 pthread_setname_np 函数为线程设置了一个有意义的名字。这个步骤在实际项目中强烈建议实施。如果不设置,所有线程在 top -H 或 ps -T 中的名字都会与进程名相同,如下图所示,这将给问题定位带来巨大困难。

使用 pthread_setname_np 时需要注意几点:
1. 线程名长度限制
Linux内核限定线程名(comm字段)最多为16个字节,其中包含一个终止符 \0,因此实际可用的字符最多为15个。超过此长度,pthread_setname_np 会返回 ERANGE 错误。建议使用简短明了的名字,如 net_rx、db_sync。
2. pthread_setname_np 与 prctl 的区别
另一种设置线程名的方法是使用 prctl(PR_SET_NAME, “thread_name”)。两者的主要区别在于:prctl 只能设置调用者自身所在线程的名字,而 pthread_setname_np 可以通过线程句柄设置任意线程的名字。在创建线程时从外部进行统一命名,pthread_setname_np 更为方便。
3. 养成命名的好习惯
许多项目在创建线程后便忽略命名,导致后期调试和维护成本增加。建议在项目初期就将线程命名规范纳入考量,这是一个投入小但长期回报高的好习惯。
总结
面对嵌入式Linux中多线程程序的资源占用问题,我们介绍了四种定位方法:
top -H:实时监控,直观高效,是日常调试的首选工具。
ps -T:获取瞬时快照,输出格式灵活,便于集成到脚本中。
pidstat -t:定时采样,输出带时间戳的规整数据,适合分析CPU使用趋势和间歇性问题。
/proc/<pid>/task/:最底层的数据接口,是工具缺失环境下的终极解决方案,也为深入理解Linux线程模型提供了窗口。
在实际工作中,你可以根据具体场景灵活选用。掌握这些方法,下次再遇到“CPU被哪个线程吃掉了”这种问题时,就能做到心中有数,快速定位。如果你有更多关于嵌入式开发或系统调试的心得,欢迎在云栈社区与大家交流分享。