死锁发生时需让各线程主动采集自身调用栈:通过pthread_kill(tid, sigusr1)发送信号,由目标线程在async-signal-safe的信号处理函数中调用backtrace()和backtrace_symbols_fd()写入文件描述符,避免跨线程远程抓栈错误;同时配合loggedmutex记录锁路径,并构建锁依赖图定位循环等待环。

死锁发生时怎么拿到线程的完整调用栈
Linux 下 C++ 程序死锁本身不会自动触发崩溃或信号,所以不能靠 sigaction 捕获。真正可行的方式是:在检测到疑似死锁(比如锁等待超时)时,主动从每个阻塞线程中抓取当前调用栈。glibc 提供 backtrace() 和 backtrace_symbols(),但它们只对当前线程有效 —— 你得在目标线程上下文中调用才准。
常见错误是主线程试图“远程”获取其他线程栈,结果拿到的是主线程自己的栈。正确做法是让每个线程在尝试加锁前注册回调,或使用信号机制(如 pthread_kill(tid, SIGUSR1))触发目标线程自采栈。注意:必须确保信号处理函数里只调用 async-signal-safe 函数,backtrace() 是安全的,但 backtrace_symbols() 不是,应改用 backtrace_symbols_fd() 直接写入文件描述符。
用 std::mutex 配合 RAII 记录锁路径的最小侵入方案
不改业务逻辑的前提下,把原始 std::mutex 替换为带日志能力的封装类,比如 LoggedMutex。它在 lock() 前记录当前线程 ID、锁地址、调用位置(用 __FILE__、__LINE__、__func__),在 unlock() 时清除。关键点在于:不要在构造/析构里打日志(可能跨线程析构),只在加锁/解锁点记录。
示例片段:
class LoggedMutex {
std::mutex mtx_;
mutable std::atomic<bool> locked_{false};
public:
void lock() {
if (!locked_.exchange(true, std::memory_order_acquire)) {
log_lock_attempt(__FILE__, __LINE__, __func__);
}
mtx_.lock();
}
void unlock() {
mtx_.unlock();
locked_.store(false, std::memory_order_release);
}
};
</bool>
注意:locked_ 标记仅用于避免重复记录,不是线程安全判断依据;真实互斥仍由 mtx_ 保证。
检测到死锁后如何把多个线程栈合并成可读日志
单纯打印各线程栈不够,需要标注「谁在等谁」。典型做法是维护一个全局锁依赖图(Lock Dependency Graph):每次加锁时记录 (thread_id, lock_addr, waiting_for),解锁时删除对应边。当发现某个线程等待的锁正被另一线程持有,且后者又在等前者 —— 就是环,即死锁。
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
此时 dump 日志应包含:
- 每个参与死锁的线程 ID 和当前调用栈(用
backtrace_symbols_fd()写入临时 fd) - 每条锁持有关系:
thread A holds lock 0x7f... and waits for lock 0x7f... - 对应源码位置(需提前用
addr2line -e ./myapp -f -C处理地址,或运行时调用system()执行,但要注意 fork 安全)
避免直接在信号处理函数里做复杂解析,建议只写原始地址 + tid 到 ring buffer,另起监控线程异步转义。
为什么 addr2line 解析失败或显示 ??
常见原因有三个:二进制没带调试符号(编译未加 -g)、链接时 strip 过、或者函数被内联导致帧丢失。验证方法:readelf -w ./myapp | head 看是否有 DWARF 段;nm -C ./myapp | grep your_func_name 确认符号存在。
若线上环境不能留调试信息,可用 -gmlt(只保留行号信息,体积小)或结合 build ID:编译时加 -Wl,--build-id,运行时从 /proc/self/exe 读 build ID,再查匹配的 debug 文件。
还有一个容易忽略的点:C++ 异常栈和 pthread_cancel 栈格式不同,backtrace() 对被 cancel 的线程可能截断,务必在 pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, ...) 保护下采集。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!










