用top -H -p 定位高CPU线程,转TID为十六进制后用perf record -t -g采样,再通过perf report -g --no-children分析热点函数,可快速锁定死循环、忙等待、条件变量误用或第三方回调阻塞等问题。

用 top + perf 找出高CPU线程和热点函数
程序跑起来后CPU飙到100%,第一反应不是翻代码,而是确认「到底哪个线程在狂转」。Linux下最直接的方式是用 top -H -p (-H 显示线程),按 Shift+P 按CPU排序,一眼就能看到占用最高的线程ID(LWP列)。记下这个十进制TID,转成十六进制(比如 printf "%x\n" 12345),再用 perf record -t 采样5秒。之后 perf report -g --no-children 就能定位到具体卡在哪个函数里——常见的是死循环、无休止的 while (!flag)、或锁竞争导致的忙等待。
检查 std::thread / pthread_create 后是否 detach 或 join
主线程退出时,如果创建的子线程既没 join() 也没 detach(),C++标准规定会调用 std::terminate(),但某些场景下(尤其调试器未捕获时)可能表现为进程僵死+CPU持续占用。更隐蔽的是:调用了 detach(),但线程内部逻辑有无限重试(比如网络连接失败后 while (true) { connect(); usleep(1000); }),没有退避策略,实际就是个自旋。排查时重点看所有线程启动点:
-
std::thread对象生命周期是否超出作用域前已join()或detach() - pthread 场景下是否漏了
pthread_detach()或忘记pthread_join() - 线程函数末尾是否有隐式无限循环(比如
while (running) { ... }但running被编译器优化掉,没加volatile或用std::atomic)
警惕 std::condition_variable::wait() 缺少谓词或虚假唤醒处理
写条件变量等待时,只写 cv.wait(lock) 不带谓词,是高频CPU 100%来源。因为 wait() 返回后不检查条件是否真满足,就直接往下走,结果立刻又进 wait() ——形成「唤醒→检查→没满足→立即再等」的假性循环。正确写法必须带谓词:cv.wait(lock, [&] { return ready; });。即使带了谓词,如果 ready 是普通 bool 且没用 std::atomic 保护,可能因内存序问题导致等待线程永远看不到变更;或者生产者端忘了调用 cv.notify_one()/notify_all(),消费者线程被永久挂起,而其他线程还在空转轮询。
确认第三方库回调是否陷入同步阻塞或重入
用了 libuv、Boost.Asio、Qt 等异步库时,容易忽略其回调执行上下文。例如 Qt 的 QTimer::singleShot(0, ...) 回调在事件循环中同步执行,若回调里调用了阻塞IO(如 fread()、recv())或递归触发自身(比如处理完消息又发个新消息),就会卡住整个事件线程,表面看是主线程CPU 100%,实际是同步等待。同样,libuv 的 uv_run() 如果传入 UV_RUN_DEFAULT 但没配好文件描述符监听或 timer,也可能因无事件可处理却不断轮询而吃满CPU。验证方法:临时注释掉所有自定义回调,看CPU是否回落;再逐个打开,定位到具体回调函数。
真正难排查的,往往是多个小问题叠加:一个没 join() 的线程 + 一个没谓词的 wait() + 一个阻塞的第三方回调。它们单独存在时可能不显山露水,合在一起就让CPU稳稳钉在100%。动手前先用 perf 定线程、定函数,比对着代码瞎猜快得多。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











