显示正在运行的Python应用程序的堆栈跟踪

399 投票
29 回答
179499 浏览
提问于 2025-04-11 09:26

我有一个Python应用程序,有时候会卡住,我找不到原因。

有没有办法让Python解释器告诉我正在运行的具体代码呢?

有没有什么方法可以实时查看调用栈的信息?

相关问题:

29 个回答

81

我几乎总是在处理多个线程,而主线程通常没什么事情可做,所以我最感兴趣的是查看所有线程的状态(这更像是Java中的状态转储)。下面是基于这个博客的实现:

import threading, sys, traceback

def dumpstacks(signal, frame):
    id2name = dict([(th.ident, th.name) for th in threading.enumerate()])
    code = []
    for threadId, stack in sys._current_frames().items():
        code.append("\n# Thread: %s(%d)" % (id2name.get(threadId,""), threadId))
        for filename, lineno, name, line in traceback.extract_stack(stack):
            code.append('File: "%s", line %d, in %s' % (filename, lineno, name))
            if line:
                code.append("  %s" % (line.strip()))
    print("\n".join(code))

import signal
signal.signal(signal.SIGQUIT, dumpstacks)
158

建议安装信号处理器是个不错的主意,我经常用到这个方法。比如,bzr 默认会安装一个 SIGQUIT 的处理器,这个处理器会调用 pdb.set_trace(),让你立刻进入 pdb 的调试界面。(具体细节可以查看 bzrlib.breakin 模块的源代码。)在 pdb 中,你不仅可以查看当前的调用栈(用 (w)here 命令),还可以检查变量等等。

不过,有时候我需要调试一个我没有提前安装信号处理器的进程。在 Linux 系统上,你可以把 gdb 附加到这个进程上,然后用一些 gdb 的宏来获取 Python 的调用栈。你只需把 http://svn.python.org/projects/python/trunk/Misc/gdbinit 放到 ~/.gdbinit 文件中,然后:

  • 附加 gdb:gdb -p PID
  • 获取 Python 调用栈:pystack

不过,这个方法并不是完全可靠,但大多数时候都能用。你也可以参考 https://wiki.python.org/moin/DebuggingWithGdb

最后,使用 strace 也常常能让你了解一个进程在做什么。

353

我有一个模块可以用在这种情况——当一个程序需要长时间运行,但有时会因为一些未知的原因卡住。这种方法有点小技巧,而且只适用于unix系统(需要使用信号):

import code, traceback, signal

def debug(sig, frame):
    """Interrupt running process, and provide a python prompt for
    interactive debugging."""
    d={'_frame':frame}         # Allow access to frame object.
    d.update(frame.f_globals)  # Unless shadowed by global
    d.update(frame.f_locals)

    i = code.InteractiveConsole(d)
    message  = "Signal received : entering python shell.\nTraceback:\n"
    message += ''.join(traceback.format_stack(frame))
    i.interact(message)

def listen():
    signal.signal(signal.SIGUSR1, debug)  # Register handler

使用时,只需在程序启动时的某个地方调用listen()函数(你甚至可以把它放在site.py里,这样所有的python程序都可以用),然后让它运行。任何时候,你都可以通过kill命令发送一个SIGUSR1信号给这个进程,或者在python中这样做:

    os.kill(pid, signal.SIGUSR1)

这样做会让程序在当前运行的位置中断,进入一个python控制台,显示出堆栈跟踪信息,并让你可以操作变量。使用control-d(EOF)可以继续运行(不过要注意,你可能会在发送信号的那一刻中断任何输入输出操作,所以这并不是完全不干扰的)。

我还有另一个脚本,功能类似,只不过它是通过管道与正在运行的进程通信(这样可以调试后台进程等)。这个脚本有点长,不能在这里全部贴出来,但我把它放在了一个python食谱里。

撰写回答