strace -p 在多线程下不可信,因其依赖 ptrace 导致调度紊乱、阻塞调用耗时失真、输出混杂无序、高频率下自身成瓶颈,且默认不区分线程;应改用 perf trace 结合 perf record 定位真实锁争用与调用栈。

不能用 strace -p 直接 attach 进程并指望它准确反映多线程系统调用耗时——它会严重干扰调度、掩盖真实行为,且默认不区分线程粒度。
为什么 strace 在多线程下不可信
strace 依赖 ptrace,每次系统调用都会强制目标线程暂停、切到 strace 进程处理,再切回。这导致:
- 线程调度被完全打乱,
FUTEX_WAIT等阻塞调用的等待时间被大幅拉长,误判为“锁卡死” - 多个线程并发触发同一类系统调用(如
write)时,strace输出混杂无序,无法对齐时间戳 - 高频率调用(如每微秒一次
gettimeofday)会让strace自身成为瓶颈,甚至拖垮目标进程 - 默认不记录线程 ID(LWP),
-f选项虽能跟踪子线程,但输出仍按事件顺序平铺,无法按线程聚合分析
用 perf trace 精确捕获每个线程的 futex 和 I/O 耗时
真正可行的方式是用 perf trace,它基于内核 tracepoint,开销极低(通常
- 只跟踪关键系统调用:避免全量捕获带来的噪音和磁盘压力
perf trace -e 'syscalls:sys_enter_futex,syscalls:sys_enter_read,syscalls:sys_enter_write' -t -p <pid> -
-t是必须的:启用线程 ID(LWP)标记,输出中每行带[tid]前缀 - 关注
futex的val和timeout字段:
•val=1表示进入FUTEX_WAIT;
•timeout != 0且持续 >100μs,基本确认锁争用 - 对
read/write,注意看返回值:
• 返回-1且errno=11 (EAGAIN)是非阻塞 I/O 正常行为;
• 返回-1且errno=4 (EINTR)可能被信号打断,需重试逻辑检查
配合 perf record 定位系统调用在调用栈中的位置
知道哪个线程在调什么还不够,得知道是哪段 C++ 代码触发的——perf record 能把系统调用和用户态栈关联起来。
立即学习“C++免费学习笔记(深入)”;
- 启用调用图并过滤系统调用事件:
perf record -g -e 'syscalls:sys_enter_futex,syscalls:sys_enter_write' -t -p <pid> sleep 5 - 生成带上下文的报告:
perf script | grep -A 5 'futex'查看紧邻的 C++ 函数名 - 关键点:编译时必须带
-g,且避免过度优化(如-O2可能内联掉栈帧),推荐用-Og -g - 若看到大量
std::mutex::lock→__lll_lock_wait→sys_enter_futex链路,说明锁竞争发生在用户代码层,不是内核问题
别忽略 /proc/<pid>/status 里的 Threads 字段
系统调用耗时异常,有时根本不是代码问题,而是线程数失控导致内核调度压力激增。
- 实时检查当前线程总数:
grep Threads /proc/<pid>/status—— 若数值远超预期(比如设计为 8 线程,却显示 200+),先查std::thread是否泄漏或未join()/detach() - 对比
perf stat -t -p <pid>输出中的context-switches:如果每秒上下文切换 >10k,且Threads数值高,大概率是线程爆炸引发的内核调度抖动,而非单个系统调用慢 - 容器环境同样适用该路径,只要 procfs 挂载正常(默认都挂载)
最易被忽略的是:系统调用耗时升高,常常不是因为 read 或 futex 本身变慢,而是线程数过多导致内核栈分配竞争、TLB 刷新加剧、缓存行伪共享恶化——这些都不会直接出现在 strace 或 perf trace 的单行输出里,必须结合 /proc/<pid>/status 和 perf stat -e cache-misses 交叉验证。


















