top -h 显示线程 cpu 占用虚高是因未归一化到单核基准,且默认不显示线程名;需按 h、f 启用 time+ 和 wchan,再 shift+p 排序,结合 /proc/tid/comm、jstack 或 perf 进一步定位。

Top -H 为什么看不到线程 CPU 占用的准确值
直接运行 top -H 后,你可能会发现所有线程的 %CPU 列加起来远超 100%,甚至达到几百——这不是 bug,而是 top 默认按“最近采样周期内该线程占用 CPU 时间占比”计算,且未归一化到单核基准。在多核机器上,一个满载线程可能显示为 99.7%,但两个同核竞争的线程可能各自显示 80%+,总和虚高。更关键的是:top -H 默认不显示线程名(TID 对应的 COMMAND 是进程名),你根本分不清哪个 TID 属于哪个逻辑线程。
必须开启的三个 top 交互命令
进入 top -H 后,仅靠默认视图无法定位死循环线程。需立即执行以下操作:
- 按
H键(确认已处于线程模式,状态栏显示Threads: on) - 按
f进入字段管理 → 用方向键选中TIME+(累计 CPU 时间)→ 按空格启用 → 再选中WCHAN(线程当前等待的内核函数,死循环时此项常为空或-)→ 按空格启用 - 按
Shift + P按%CPU降序排列(不是小写 p)
此时真正持续跑飞的线程会同时满足:① %CPU 接近 100/n(n 为逻辑 CPU 数,如 8 核机器≈12.5% 单线程上限);② TIME+ 增长极快;③ WCHAN 为空或 -(说明没阻塞,纯用户态空转)。
如何把 TID 映射回具体线程名和调用栈
top -H 只给 TID(线程 ID),但你需要知道这是 Java 的 GC 线程、Go 的 goroutine 调度器,还是你的业务线程。方法如下:
Zynq‑7000 / Zynq UltraScale+ 硬件调试技能。适用于 Xilinx Zynq(Cortex‑A9/A53 + FPGA)Linux 环境,调试 I2C、I2S、UART、GPIO 等外设。包括设备树配置、内核驱动、用户空间调试工具、交叉编译和常见问题排查,支持 PetaLinux 开发。
- 记下可疑
TID(比如12345),退出top,执行:ls -l /proc/12345/comm
→ 直接输出线程名(如grpc-server) - 看完整命令行:
cat /proc/12345/cmdline | tr '\0' ' '
- 查堆栈(需有调试符号或
gdb权限):gdb -p 12345 -ex "thread apply all bt" -ex "quit" 2>/dev/null | grep -A5 -B5 "while\|for\|loop"
对 Java 进程,优先用 jstack <pid></pid> 并搜索 java.lang.Thread.State: RUNNABLE 且无 parking/waiting 的栈帧;Go 程序用 kill -6 <pid></pid> 触发 panic 输出 goroutine 栈。
比 top -H 更准的替代方案
当 top -H 因采样间隔(默认 3s)错过短时爆发或被干扰时,用以下命令补位:
- 实时线程级火焰图(需
perf):perf record -e cycles:u -t <tid> -g -- sleep 5 && perf script | FlameGraph/stackcollapse-perf.pl | FlameGraph/flamegraph.pl > thread.svg</tid>
- 查看每个线程精确的 CPU 使用率(百分比归一化):
ps -T -o tid,%cpu,time,comm -p <pid> | sort -k2nr | head -10</pid>
- 检查是否因锁争用导致伪死循环:
pidstat -t -p <pid> 1 3 | grep -E "(TGID|<code>TID</code>)"</pid>
→ 观察%usr和%sys比例,若%sys异常高,可能是 futex 等系统调用自旋
真正难缠的死循环往往藏在信号处理、定时器回调或异步 I/O 完成队列里,top -H 只是第一道筛子——它能告诉你“谁在猛吃 CPU”,但“为什么停不下来”,还得靠 /proc/<tid>/stack</tid> 或语言专属诊断工具。










