当前位置:首页 > 文章列表 > 文章 > python教程 > Python faulthandler.dump_traceback_later 怎么定位卡住的线程

Python faulthandler.dump_traceback_later 怎么定位卡住的线程

来源:17golang原创 2026-11-05 15:01:47 0浏览 收藏

我第一次用 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 只输出一次,适合先判断卡在哪个函数。如果一次快照已经足够,不需要为了“看起来更完整”打开重复模式。

Python watchdog 计时器、线程栈快照和业务进程之间的静态关系说明图
图1:说明图展示 dump_traceback_later 的 watchdog、timeout 和线程栈快照关系,不是运行截图或执行证据。

把栈写进独立文件,别让现场淹没在业务日志里

接下来我会把输出放到一个独立文件。这样做的关键不是文件名,而是文件句柄的生命周期:官方文档提醒,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 调用,先检查连接、读取超时和上游依赖。
  • 线程栈位置不断变化但请求仍不结束,说明它可能是在工作,而不是完全死锁,需要再结合业务日志判断。
重复线程栈快照写入诊断文件并按时间对照卡顿位置的静态关系说明图
图2:结构图说明 repeat 周期、诊断文件和重复调用链之间的对照关系,不代表真实线上日志。

和普通 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 触发前离开代码块,是最容易让现场丢失的写法。文件轮转、描述符复用和进程退出也都应纳入排查脚本的清理顺序。

我会怎样把它放进一次卡顿排查

  1. 先设一个不会马上误报的 timeout,用 repeat=False 抓第一份快照。
  2. 把输出写到独立文件,保证文件在转储完成前保持打开。
  3. 若问题持续或第一次快照不够,再改为 repeat=True,通过多份栈比较稳定位置。
  4. 每次诊断结束都调用 cancel_dump_traceback_later(),再关闭输出文件。
  5. 根据重复出现的调用链决定下一步是查锁、查 I/O 还是启用专门的性能分析工具。

这套方法的价值在于先获得一份时间点证据,而不是马上把所有诊断工具都接入生产进程。它足够轻量,却能把“服务卡住了”缩小成具体线程、函数和等待位置。

相关问题

dump_traceback_later 会只打印当前线程吗?

默认目标是所有线程的 Python traceback。它的用途正是从全局线程快照中寻找卡顿位置,而不是只查看调用配置函数的线程。

timeout 到期后程序会自动退出吗?

不会。只有显式设置 exit=True 才会在转储后调用立即退出逻辑;普通定位建议保留默认值。

为什么日志文件里没有线程栈?

先检查计时器是否已被取消、timeout 是否尚未到期,以及传入的文件是否仍保持打开。若文件被轮转或描述符被复用,也要重新建立诊断文件与计时器的绑定。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
Python warnings.catch_warnings 怎么把指定警告临时升级为异常Python warnings.catch_warnings 怎么把指定警告临时升级为异常
上一篇
Python warnings.catch_warnings 怎么把指定警告临时升级为异常
Python pathlib.PurePath.relative_to 的 walk_up 参数怎么处理父目录路径
下一篇
Python pathlib.PurePath.relative_to 的 walk_up 参数怎么处理父目录路径
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • PubMedQA数据集详解:生物医学问答基准、功能与应用指南
    PubMedQA
    深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
    893次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    896次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    981次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    968次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    794次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议 和 隐私政策
返回登录
  • 重置密码