我有一个python脚本,我怀疑存在死锁。我试图用pdb
进行调试,但是如果我一步一步地进行调试就不会出现死锁,并且通过返回的输出我可以看到它没有被挂在同一个迭代上。我想将我的脚本仅在它被锁定时附加到调试器,是否可能?如有必要,我可以使用其他调试器。
答案 0 :(得分:35)
目前, pdb 无法暂停并开始在正在运行的程序上进行调试。您还有其他一些选择:
<强> GDB 强>
您可以使用GDB在C级别进行调试。这有点抽象,因为你在探索Python的C源代码而不是实际的Python脚本,但它对某些情况很有用。说明如下:https://wiki.python.org/moin/DebuggingWithGdb。他们太过牵扯到这里总结一下。
第三方扩展程序&amp;模块强>
谷歌搜索“pdb附加过程”揭示了一些项目给PDB这种能力:
注射器:https://github.com/google/pyringe
Pycharm:https://blog.jetbrains.com/pycharm/2015/02/feature-spotlight-python-debugger-and-attach-to-process/
Python wiki的这个页面有几个选择:https://wiki.python.org/moin/PythonDebuggingTools
对于您的具体用例,我有一些解决方法的想法:
<强>信号强>
如果您使用的是unix,则可以使用signals中的this blog post来尝试暂停并附加到正在运行的脚本。
此报价单块直接从链接的博客文章中复制:
当然pdb已经有了在程序中间启动调试器的函数,最值得注意的是pdb.set_trace()。但是,这需要您知道要开始调试的位置,这也意味着您不能将其留在生产代码中。
但是我总是羡慕我能用GDB做什么:只是打断一个正在运行的程序并开始使用调试器。这在某些情况下可能很方便,例如:你陷入困境,想要调查。今天我突然想到:只需注册一个设置跟踪功能的信号处理程序!这里是概念证明代码:
import os import signal import sys import time def handle_pdb(sig, frame): import pdb pdb.Pdb().set_trace(frame) def loop(): while True: x = 'foo' time.sleep(0.2) if __name__ == '__main__': signal.signal(signal.SIGUSR1, handle_pdb) print(os.getpid()) loop()
现在我可以将SIGUSR1发送到正在运行的应用程序并获得一个调试器。可爱!
我想你可以通过使用Winpdb来实现远程调试,以防你的应用程序不再连接到终端。上面代码的另一个问题是,在调用pdb之后似乎无法恢复程序,退出pdb之后你只是得到一个回溯并完成(但是因为这只是bdb引发了bdb.BdbQuit异常我猜这可以通过几种方式解决)。最后一个直接问题是在Windows上运行它,我对Windows不太了解,但我知道它们没有信号,所以我不知道你怎么能在那里做到这一点。
如果您没有可用的信号,如果您将锁或信号量采集包装在一个递增计数器的循环中,您可能仍然可以使用PDB,并且只有在计数达到一个非常大的数字时才停止。例如,假设你有一个锁,你怀疑它是你死锁的一部分:
lock.acquire() # some lock or semaphore from threading or multiprocessing
以这种方式改写:
count = 0
while not lock.acquire(False): # Start a loop that will be infinite if deadlocked
count += 1
continue # now set a conditional breakpoint here in PDB that will only trigger when
# count is a ridiculously large number:
# pdb> <filename:linenumber>, count=9999999999
当count非常大时(希望)表示在那里发生了死锁,断点应该触发。如果您发现它在锁定对象似乎没有指示死锁时触发,那么您可能需要在循环中插入一个短时间延迟,因此它不会非常快地增加。您还可能需要使用断点的触发阈值来使其在正确的时间触发。我的例子中的数字是任意的。
另一个变体就是不使用PDB,并在计数器变大时故意引发异常,而不是触发断点。如果编写自己的异常类,可以使用它来捆绑异常中的所有本地信号量/锁定状态,然后在脚本的顶层捕获它以在退出之前打印出来。
使用死锁循环而不依赖于正确获取计数器的另一种方法是写入文件:
import time
while not lock.acquire(False): # Start a loop that will be infinite if deadlocked
with open('checkpoint_a.txt', 'a') as fo: # open a unique filename
fo.write("\nHit") # write indicator to file
time.sleep(3) # pause for a moment so the file size doesn't explode
现在让你的程序运行一两分钟。杀死程序并浏览那些“检查点”文件。如果死锁是导致程序停滞的原因,那么在其中写入“hit”一词的文件会指示哪些锁定采集会导致死锁。
您可以通过循环打印变量或其他状态信息而不仅仅是常量来扩展其有用性。例如,您说您怀疑死锁是在循环中发生但不知道它在哪个迭代。让这个锁循环转储你的循环的控制变量或其他状态信息,以识别发生死锁的迭代。
答案 1 :(得分:10)
有一个pdb的克隆,富有想象力地称为pdb-clone,可以attach to a running process。
您只需将from pdb_clone import pdbhandler; pdbhandler.register()
添加到主进程的代码中,然后就可以使用pdb-attach --kill --pid PID
启动pdb。
答案 2 :(得分:1)
使用pyrasite:
>>> pyrasite 172483 dump_stacks.py
...其中172483是正在运行的python进程的PID。然后,python进程将为每个线程打印堆栈跟踪。可以发送任意要执行的Python代码或打开外壳。
这对于调试死锁非常有用。悬挂过程开始后,甚至可以安装吡pyr石。但是请注意,您应该将其安装在相同的环境中以使其正常工作。
这不是唯一可用的工具,但是由于某种原因,似乎很难偶然发现它。它很旧,但是像Python 2和3一样具有魅力。
此工具可能不像大多数使用unix头文件用于本机C函数的注入器那样支持win32。 this open issue。
答案 3 :(得分:0)
您可以使用我的项目 madbg。它是一个 python 调试器,允许您附加到正在运行的 python 程序并在当前终端中调试它。它类似于 pyrasite
和 pyringe
,但支持 python3,不需要 gdb,并且使用 IPython
作为调试器(这意味着带有颜色和自动完成功能的 pdb)。
例如,也可以查看您的脚本卡在何处,您可以运行:
madbg attach <pid>
在调试器 shell 中,输入:
bt