找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

1706

积分

0

好友

224

主题
发表于 15 小时前 | 查看: 0| 回复: 0

在嵌入式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 的参数。

运行效果如下图所示,它清晰地展示了进程内每个线程的实时状态。

top -H 命令输出展示各线程详情

我们来解读一下输出中几个关键列:

  • 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 按线程采样

pidstatsysstat 工具包中的一个命令,专为性能分析设计。它可以按固定时间间隔对线程的CPU使用率进行采样,输出格式规整且自带时间戳。

pidstat -t -p `pidof multi_thread` 1

其中 -t 参数表示显示线程级别的信息,末尾的 1 代表每隔1秒采样一次。

这个方法非常适合用来分析趋势或定位间歇性的CPU使用率“毛刺”。比如,你怀疑某个线程会周期性飙升CPU,用 pidstat 持续采集几分钟的数据,就很容易在输出中发现规律。

方法四:直接读取 /proc 文件系统

前面介绍的命令工具,其数据源头都是Linux的 /proc 虚拟文件系统。在极端情况下(例如系统极度精简,未安装 topps 等工具),直接读取 /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 -Hps -T 中的名字都会与进程名相同,如下图所示,这将给问题定位带来巨大困难。

未设置线程名时,所有线程名均显示为进程名

使用 pthread_setname_np 时需要注意几点:

1. 线程名长度限制
Linux内核限定线程名(comm字段)最多为16个字节,其中包含一个终止符 \0,因此实际可用的字符最多为15个。超过此长度,pthread_setname_np 会返回 ERANGE 错误。建议使用简短明了的名字,如 net_rxdb_sync

2. pthread_setname_np 与 prctl 的区别
另一种设置线程名的方法是使用 prctl(PR_SET_NAME, “thread_name”)。两者的主要区别在于:prctl 只能设置调用者自身所在线程的名字,而 pthread_setname_np 可以通过线程句柄设置任意线程的名字。在创建线程时从外部进行统一命名,pthread_setname_np 更为方便。

3. 养成命名的好习惯
许多项目在创建线程后便忽略命名,导致后期调试和维护成本增加。建议在项目初期就将线程命名规范纳入考量,这是一个投入小但长期回报高的好习惯。

总结

面对嵌入式Linux中多线程程序的资源占用问题,我们介绍了四种定位方法:

  1. top -H:实时监控,直观高效,是日常调试的首选工具。
  2. ps -T:获取瞬时快照,输出格式灵活,便于集成到脚本中。
  3. pidstat -t:定时采样,输出带时间戳的规整数据,适合分析CPU使用趋势和间歇性问题。
  4. /proc/<pid>/task/:最底层的数据接口,是工具缺失环境下的终极解决方案,也为深入理解Linux线程模型提供了窗口。

在实际工作中,你可以根据具体场景灵活选用。掌握这些方法,下次再遇到“CPU被哪个线程吃掉了”这种问题时,就能做到心中有数,快速定位。如果你有更多关于嵌入式开发或系统调试的心得,欢迎在云栈社区与大家交流分享。




上一篇:告别React束缚?聊聊HTMX如何提升开发效率与降低项目复杂度
下一篇:上海交大提出JTok-M:以Token嵌入为新Scaling维度,节省35%算力成本
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-3-3 19:55 , Processed in 1.594631 second(s), 46 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表