Python faulthandler.dump_traceback_later 怎么定位卡住的线程
来源:17golang原创
时间:2026-11-05 15:01:47 444浏览 收藏
我第一次用 faulthandler.dump_traceback_later(),是在一个“进程还活着、接口却迟迟不返回”的 Python 服务上。这个函数很适合做第一轮现场记录:给它一个超时时间,时间到了就把所有线程的 Python 调用栈写出来;如果把 repeat 设为 True,还可以按固定周期留下多份快照。
官方地址:https://docs.python.org/3/library/faulthandler.html#faulthandler.dump_traceback_later
定位卡住的线程时,先用一次性超时抓现场,再按需要开启重复转储;排查结束一定调用取消函数。exit=True 会直接结束进程,不适合作为普通卡顿诊断的默认配置。
这个函数解决的是哪一类卡顿
dump_traceback_later() 适合处理“程序没有崩溃,但一段时间没有进展”的场景,例如请求线程等待锁、工作线程卡在阻塞 I/O,或者某个任务长期停留在同一段 Python 调用链上。它不是性能剖析器,也不会告诉你每个函数消耗了多少 CPU;它留下的是某个时间点的线程栈。
官方文档把它实现为一个 watchdog 线程。默认情况下,超时后会把所有线程的 traceback 写到 sys.stderr。这意味着它可以在主线程疑似卡住时仍然尝试输出诊断信息,但输出位置必须先规划好,不能只盯着业务日志猜。
第一次试用:先抓一份一次性线程栈
我通常先把 repeat 保持默认的 False,只观察一次超时。下面的例子把等待时间设为 10 秒,函数会在计时到点后打印线程栈,而业务代码仍按原来的逻辑继续运行。
import faulthandler
import time
# 10 秒没有完成时输出一次所有线程的 Python 栈,先保留现场
faulthandler.dump_traceback_later(10, repeat=False)
try:
# 用真实业务调用替换这里;示例只模拟一个长时间等待
time.sleep(30)
finally:
# 业务提前完成时取消 watchdog,避免退出后再产生无关转储
faulthandler.cancel_dump_traceback_later()
这里有两个容易误解的点。第一,10 是秒数,不是“最多运行 10 秒”;它只决定何时输出栈。第二,repeat=False 只输出一次,适合先判断卡在哪个函数。如果一次快照已经足够,不需要为了“看起来更完整”打开重复模式。

把栈写进独立文件,别让现场淹没在业务日志里
接下来我会把输出放到一个独立文件。这样做的关键不是文件名,而是文件句柄的生命周期:官方文档提醒,dump_traceback_later() 会持有传入文件的文件描述符,直到线程栈转储完成或取消计时器;如果文件提前关闭,或者底层描述符被复用,输出可能落到意外的位置。
import faulthandler
import time
# 文件需要一直保持打开,直到转储完成或显式取消计时器
with open("traceback-dump.log", "a", encoding="utf-8") as dump_file:
faulthandler.dump_traceback_later(
10,
repeat=False,
file=dump_file,
)
try:
# 这里替换成可能卡住的任务;不要在转储前关闭 dump_file
run_one_request()
finally:
# 先取消计时器,再让 with 关闭文件,避免 watchdog 继续使用旧描述符
faulthandler.cancel_dump_traceback_later()
如果日志系统会轮转文件,也要把轮转动作和计时器一起考虑:文件被替换后,旧的文件描述符关系不会自动变成新文件。对一次性排障来说,最稳妥的做法是让诊断文件在整个探针生命周期内保持打开,并在结束时按“取消计时器、再关闭文件”的顺序清理。
持续卡顿时,repeat=True 才有价值
一次性转储只能回答“10 秒这一刻线程在哪里”。如果问题会周期性出现,或者第一次快照刚好落在正常等待上,我会把 repeat=True 打开,让相同的 timeout 作为采样间隔。
import faulthandler
import time
with open("traceback-periodic.log", "a", encoding="utf-8") as dump_file:
# 每 15 秒输出一份线程栈,观察卡顿位置是否持续不变
faulthandler.dump_traceback_later(15, repeat=True, file=dump_file)
try:
serve_until_stopped()
finally:
# 服务恢复或结束排障后停止后续周期性转储
faulthandler.cancel_dump_traceback_later()
分析重复快照时,我不会只看某一行,而是比较多份记录中反复出现的调用链:
- 同一个线程持续停在锁获取附近,优先检查锁的持有者和释放路径。
- 多个快照都停在阻塞 I/O 调用,先检查连接、读取超时和上游依赖。
- 线程栈位置不断变化但请求仍不结束,说明它可能是在工作,而不是完全死锁,需要再结合业务日志判断。

和普通 traceback、性能剖析器有什么区别
traceback.print_stack() 更适合在你已经拿到某个执行位置时主动打印当前线程;dump_traceback_later() 的优势是把“等待一段时间后自动采样所有线程”这件事交给 watchdog,主线程即使暂时没有机会执行到日志语句,也有机会留下现场。
它和 cProfile、采样型 profiler 的目标也不同:faulthandler 给出的是静态快照,不提供函数耗时、调用次数或火焰图。我的实践顺序通常是先用一次或几次线程栈确认“卡住在哪里”,确认是持续 CPU、锁竞争还是外部 I/O 后,再决定是否需要更重的性能工具。
| 工具 | 更适合回答的问题 | 输出特点 |
|---|---|---|
dump_traceback_later() | 超时那一刻各线程停在哪里 | 自动、低侵入、线程栈快照 |
traceback.print_stack() | 当前代码主动打印什么 | 需要代码执行到打印点 |
| 性能剖析器 | 时间花在哪里、热点是什么 | 更丰富,但配置和开销更高 |
三个会让排查结果失真的配置
1. 把 exit=True 当成超时保护
设置 exit=True 后,转储完成会调用 os._exit() 以状态码 1 结束进程。它不会像普通清理流程那样等待缓冲区刷新或执行清理逻辑,所以更像“超时后强制退出”的故障保险,而不是日常线程定位开关。若目标只是采集线程栈,应保留默认的 False。
2. 重复调用却以为会叠加多个计时器
官方语义是:再次调用会替换之前的参数,并重置超时。配置层或重试逻辑里不要反复调用来“增加采样频率”;应先取消旧计时器,明确保留一套当前策略,或者直接用一个合适的 repeat 周期。
3. 文件关闭得太早
诊断文件必须覆盖计时器的完整生命周期。把 open() 写在局部代码块里,却在 watchdog 触发前离开代码块,是最容易让现场丢失的写法。文件轮转、描述符复用和进程退出也都应纳入排查脚本的清理顺序。
我会怎样把它放进一次卡顿排查
- 先设一个不会马上误报的
timeout,用repeat=False抓第一份快照。 - 把输出写到独立文件,保证文件在转储完成前保持打开。
- 若问题持续或第一次快照不够,再改为
repeat=True,通过多份栈比较稳定位置。 - 每次诊断结束都调用
cancel_dump_traceback_later(),再关闭输出文件。 - 根据重复出现的调用链决定下一步是查锁、查 I/O 还是启用专门的性能分析工具。
这套方法的价值在于先获得一份时间点证据,而不是马上把所有诊断工具都接入生产进程。它足够轻量,却能把“服务卡住了”缩小成具体线程、函数和等待位置。
相关问题
dump_traceback_later 会只打印当前线程吗?
默认目标是所有线程的 Python traceback。它的用途正是从全局线程快照中寻找卡顿位置,而不是只查看调用配置函数的线程。
timeout 到期后程序会自动退出吗?
不会。只有显式设置 exit=True 才会在转储后调用立即退出逻辑;普通定位建议保留默认值。
为什么日志文件里没有线程栈?
先检查计时器是否已被取消、timeout 是否尚未到期,以及传入的文件是否仍保持打开。若文件被轮转或描述符被复用,也要重新建立诊断文件与计时器的绑定。
-
369 收藏
-
344 收藏
-
464 收藏
-
327 收藏
-
443 收藏
-
319 收藏
-
395 收藏
-
292 收藏
-
412 收藏
-
270 收藏
-
文章 · python教程 | 12小时前 | Python教程 · Python commit sqlite3 rollback Connection.autocommit autocommit isolation_level 显式事务307 收藏
-
339 收藏
-
195 收藏
-
172 收藏
-
415 收藏
-
345 收藏
-
288 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习