线程负载不均可通过top -h和perf record -e sched:sched_switch直接识别:top -h可暴露单一线程高cpu而其余线程空闲,perf可定位锁竞争或调度抢占热点,pstack快照比对能发现伪空闲线程阻塞点,strace因输出冗余且无法捕获用户态自旋而不适用。

线程负载不均不是“看不见”的问题,而是 top -H 和 perf record -e sched:sched_switch 一眼就能戳穿的事实。
用 top -H 看清每个线程的 CPU 占用
这是最直接、最低成本的确认手段。普通 top 只显示进程级 CPU,掩盖了线程间的巨大差异。
- 运行
top -H -p <pid></pid>(<pid></pid>是你的 C++ 进程 ID),按P按 CPU% 排序 - 观察是否出现:一个线程长期占 95%+,其余线程持续在 0–2% 波动
- 注意线程名(
COMMAND列):如果全是your_app,说明没设置线程名,后续排查会困难——建议用pthread_setname_np或 C++11 的std::thread::native_handle()+pthread_setname_np命名关键线程
用 perf record 抓取调度切换热点
单纯看 CPU 占用高,不等于它“忙”;可能是自旋等待、锁竞争或虚假唤醒。需要看它到底在哪儿反复切出切入。
- 运行
perf record -e sched:sched_switch -g -p <pid> sleep 10</pid>(采样 10 秒) - 然后
perf script | grep -A 5 -B 5 'your_thread_name',重点找频繁切换且栈帧集中在pthread_mutex_lock、futex_wait、__lll_lock_wait的线程 - 若看到大量
sched_switch事件中,目标线程总是被切走,而切进来的是同一个其他线程(比如 worker-2 总是抢占 worker-1),大概率是锁粒度太粗或单点任务分发器(如全局队列)成了瓶颈
用 pstack + 多次快照比对识别“伪空闲”线程
有些线程看似 CPU 低,实则卡在条件变量等待、信号量阻塞或慢系统调用上,靠 top 完全看不出来。
- 连续执行 3–5 次
pstack <pid></pid>,间隔 1–2 秒,把输出保存为st1.txt、st2.txt… - 用
grep 'waiting' st*.txt或grep -E '(cond_wait|sem_wait|epoll_wait)' st*.txt找共性阻塞点 - 特别注意:如果某线程在所有快照中都停在
__pthread_cond_wait,但唤醒它的线程却始终没出现(即没人调pthread_cond_signal),说明条件变量使用逻辑有缺陷——比如漏 signal、用错 cond/var 配对、或广播误用为单播
为什么不用 strace 直接看系统调用?
strace -p <pid></pid> 对负载不均诊断效果有限,原因很实际:
- 它默认跟踪所有线程,输出爆炸式增长,根本没法聚焦;加
-f后更难区分哪个线程在干啥 - 高频短时系统调用(如
clock_gettime、gettid)会淹没真正的问题调用,且无法体现“谁在等谁” - 它看不到用户态自旋(如
while(!flag) { _mm_pause(); }),这类代码 CPU 占用高但无系统调用,strace完全静默
真正卡点往往藏在用户态同步原语和调度行为里,而不是系统调用入口。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











