程序卡住90%以上非死锁而是阻塞或活锁:CPU≈0为死锁/纯阻塞,高位为活锁,中低且不推进多因IO或同步无超时;需用ps -T和gdb查线程状态及调用栈,重点关注锁、epoll_wait、read等阻塞点。

程序卡住不动,90% 以上不是死锁,而是阻塞或活锁。先看 CPU 占用:接近 0 是死锁/纯阻塞;持续高位是活锁或忙等;中低但线程不推进,大概率是 IO 或同步等待没超时。
查线程状态和等待链
Linux 下立刻执行:ps -T -p $(pgrep your_program) 看各线程的 STAT 列。如果全是 S(sleeping)或 W(waiting),再用 gdb -p $(pgrep your_program) 进去后运行 info threads 和 thread apply all bt。重点观察哪些线程停在 pthread_mutex_lock、__lll_lock_wait、epoll_wait、recv、sem_wait 这类调用上。
- 若多个线程都卡在同一个
pthread_mutex_lock地址,说明它们都在争一个锁——可能是锁被某个已崩溃/挂起的线程持有,也可能是锁序不一致导致循环等待 - 若线程卡在
epoll_wait或read,检查对应 fd 是否设置了超时;socket 是否对端已断连但本端未检测 - 若卡在
std::condition_variable::wait,确认是否有线程漏掉notify_one或notify_all
区分死锁、活锁与无超时阻塞
死锁特征是所有相关线程全静止、CPU 接近 0、且互相持有并等待不同锁;活锁则线程状态为 R(running),gdb 里反复看到同一段重试逻辑(比如自旋锁 + std::this_thread::yield());而最常见的“卡住”其实是某条线程在 queue.pop()、std::future::get() 或数据库查询上无限等待。
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
- 死锁破局点:统一锁获取顺序,或改用
std::lock(mtx1, mtx2)同时锁定 - 活锁破局点:加随机退避(
std::this_thread::sleep_for带 jitter)、设最大重试次数 - 无超时阻塞破局点:所有阻塞调用必须带超时,如
queue.wait_pop(timeout)、future.wait_for(...)、setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, ...)
用 GDB 快速定位锁持有者
进 gdb -p PID 后,对每个疑似卡住的线程执行 bt,再用 frame N(N 是锁调用所在栈帧号)后输 info registers 查 rdi(x86_64 下第一个参数通常是 mutex 地址)。记下这个地址,再切换到其他线程,用 print *(std::mutex*)0xADDR(地址替换成实际值)看是否为已加锁状态。若某个线程的栈显示它刚调完 lock_guard 构造却没走到析构,那它就是持有者——但可能已崩溃或陷入其他等待。
立即学习“C++免费学习笔记(深入)”;
- 注意:
std::mutex在 libc++ 或 libstdc++ 中内存布局不同,直接print可能不准;更可靠的是看pthread_mutex_t的__data.__owner字段(需用print *(int*)($rdi+16)类似方式,具体偏移查对应 libc 版本) - 如果用的是
std::shared_mutex或std::recursive_mutex,gdb无法直接解析其内部状态,优先改用pthread_rwlock_t或加日志埋点
真正难的不是发现“卡在哪”,而是确认“为什么没人释放”。很多卡死最终都指向一个被遗忘的异常路径:析构函数里没 unlock、catch 块里漏了 cleanup、信号处理函数中断了锁持有状态——这些地方几乎从不写测试,也最难复现。

















