gdb可定位线程阻塞在futex锁上:用info threads找s态线程,thread apply all bt查__lll_lock_wait等调用栈,结合frame/print获取mutex地址比对持有者。

用 gdb 查看线程当前阻塞在哪个锁上
死锁或长等待往往不是“卡死了”,而是某个线程在 pthread_mutex_lock 或 std::mutex::lock() 上挂了几十秒甚至更久。直接 gdb attach <pid></pid> 后,先用 info threads 看哪些线程处于 S(sleep)状态,再对可疑线程执行 thread apply all bt。重点找调用栈里是否出现:
-
__lll_lock_wait(glibc 的 futex 等待入口) -
pthread_mutex_lock且下层是futex_wait -
std::mutex::lock→__gthread_mutex_lock→ 底层 futex
如果看到某线程的栈顶长时间停在这类函数,说明它正在等锁;配合 frame 1 和 print 可查看传入的 mutex 地址,再比对其他线程是否正持有该地址。
用 /proc/<pid>/stack</pid> 快速定位锁竞争点
不需要启动调试器,也能快速抓现场:cat /proc/<pid>/stack</pid> 每行对应一个线程的内核态调用栈(仅限 Linux)。若某线程卡在:
ffffffff810a4b50 futex_wait_queue_me+0xc0/0x120ffffffff810a59d0 futex_wait+0x100/0x270
就表明它正通过 futex 等待某个用户态锁。这个方法适合批量巡检、监控脚本集成,但无法直接看到是哪个 std::mutex 实例——需结合符号表或 core dump 进一步映射。
给 std::mutex 加时间戳埋点(无侵入式替代方案)
标准库不提供锁等待耗时统计,硬加日志会影响性能且破坏 RAII。更可行的是封装一层轻量代理:
- 定义
TimedMutex类,内部持有一个std::mutex和一个std::atomic<uint64_t></uint64_t>记录最大等待微秒数 - 在
lock()中用std::chrono::steady_clock::now()打点,调用原mutex.lock()前后计时,更新最大值 - 暴露
max_wait_us()供诊断时采样(比如 SIGUSR1 触发 dump) - 注意:不要在
try_lock路径里计时,避免干扰逻辑
这样既不影响原有使用方式(std::lock_guard<timedmutex></timedmutex> 仍可用),又能在线上灰度采集锁争用毛刺。
为什么 perf record -e sched:sched_stat_sleep 不适合测锁等待
有人想用 perf 统计睡眠时长,但要注意:sched:sched_stat_sleep 记录的是进程/线程进入可运行队列前的**调度器级睡眠时间**,而 mutex 等待多数走的是 futex 的用户态 fastpath,根本不会触发调度器睡眠。真正被计入这里的,反而是 std::condition_variable::wait 或 pthread_cond_wait 这类明确交出 CPU 的场景。所以它会严重低估真实锁等待——你看到的“平均睡眠 1ms”,可能掩盖了某次 500ms 的 mutex 竞争。
锁等待时长不是个孤立指标,它总和锁粒度、临界区长度、线程数量一起起作用。最容易被忽略的是:同一个 std::mutex 实例被跨多个模块复用,而各处临界区执行时间差异极大——这时单看“等待时长”没意义,得同步采集临界区耗时才能判断瓶颈在哪。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











