先执行 info threads 查看所有线程状态,再用 thread 切换至可疑线程,最后执行 bt 查看调用栈定位卡点;阻塞常表现为停在 pthread_mutex_lock、epoll_wait 等系统调用,需结合寄存器和符号信息综合判断。

gdb attach 进程后怎么看线程卡在哪
直接 gdb -p 附加到进程后,先用 info threads 列出所有线程及其状态。阻塞线程通常显示为 Blocked 或长时间停在 pthread_cond_wait、sem_wait、recv、epoll_wait 等系统调用上——注意看 pc(程序计数器)地址对应的符号名,不是只看线程状态字段。
关键操作是切换到可疑线程:thread (n 是 info threads 输出左侧的编号),再执行 bt 查看完整调用栈。如果栈顶是 __futex_abstimed_wait_common 或 do_futex,基本可判定在 futex 等待,常见于 mutex/condvar/semaphore 争用或未唤醒。
- 避免只看线程 ID 而忽略 LWP(轻量级进程)ID,Linux 下线程本质是 LWP,
ps -T -p可交叉验证 -
bt若显示大量??,说明缺少调试符号,需确保二进制带-g编译,且未 strip - 若栈顶是
nanosleep或clock_nanosleep,不一定是阻塞,可能是主动休眠;需结合业务逻辑判断是否合理
怎么判断是死锁还是单纯等待资源
死锁的核心特征是多个线程互相持有对方需要的锁,且都在等对方释放。用 thread apply all bt 批量打印所有线程栈后,重点检查:
- 是否有两个及以上线程都卡在
pthread_mutex_lock,且各自已持有一个 mutex(看栈中更早一层的pthread_mutex_lock调用) - 是否出现锁顺序不一致:比如线程 A 先 lock(M1) 再 lock(M2),线程 B 却先 lock(M2) 再 lock(M1)
- 是否使用了
std::mutex但没配std::lock_guard或std::unique_lock,导致异常路径下未 unlock
一个快速辅助手段是用 info registers 查看当前线程的寄存器值,若 rdi(第一个参数)指向某个 mutex 地址,再用 print *(pthread_mutex_t*)0x... 查看该 mutex 的内部状态(__data.__count 非 0 表示已被占用,__data.__owner 是持有者 tid)。
阻塞在 epoll_wait / select / poll 怎么定位源头
这类调用本身是合法阻塞,不能直接断定异常,要确认「是否本该被唤醒却没收到事件」。先用 bt 确认栈顶确实是 epoll_wait,然后:
- 用
info proc mappings或cat /proc/确认 epoll fd 对应的内核对象是否还在(需配合/maps ls -l /proc/查 fd 类型)/fd/ - 检查事件注册逻辑:是否漏掉
EPOLLIN/EPOLLOUT,或错误地用了边缘触发(ET)却没一次性读完数据,导致后续无新事件 - 若使用
eventfd或timerfd做唤醒机制,用call read(尝试手动读取,看是否能触发逻辑继续——这能验证唤醒通道是否通畅, &buf, 8)
注意:epoll_wait 返回 0 表示超时,返回 -1 且 errno == EINTR 是被信号中断,这两种都不是「假死」;只有永远不返回且无超时才可疑。
为什么 gdb 里 step 或 next 会卡住不动
因为你在阻塞系统调用上下文里单步,gdb 会尝试让目标线程继续执行一条指令,但它卡在内核态等待,用户态无法推进。这不是 gdb 故障,而是行为符合预期。
- 不要对
pthread_cond_wait或sem_wait执行step,改用continue或signal SIGCONT让它自然唤醒 - 若想观察锁竞争过程,应在加锁前下断点:
break pthread_mutex_lock,而非等卡住后再调试 - 多线程环境下慎用
set follow-fork-mode child,否则 fork 后新进程可能脱离 gdb 控制,导致「找不到线程」错觉
最易被忽略的是:GDB 默认只暂停目标线程,其他线程仍在运行。排查时务必用 set scheduler-locking on 冻结其余线程,否则你看到的栈可能瞬间过期。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











