繁体   English   中英

我怎么知道我的python脚本挂在哪里?

[英]How can I tell where my python script is hanging?

所以我在调试我的 python 程序时遇到了一个使程序挂起的错误,就像处于无限循环中一样。 现在,我之前遇到了一个无限循环的问题,但是当它挂断时,我可以杀死程序,python 吐出一个有用的异常,告诉我当我向它发送 kill 命令时程序在哪里终止。 但是,现在,当程序挂断并按 ctrl-c 时,它不会中止而是继续运行。 有什么工具可以用来定位挂断电话吗? 我是分析的新手,但据我所知,分析器只能为您提供有关已成功完成的程序的信息。 或者您可以使用分析器来调试此类挂断吗?

让我们假设您正在运行您的程序:

python YOURSCRIPT.py

尝试将您的程序运行为:

python -m trace --trace YOURSCRIPT.py

并且在屏幕上打印很多东西时要有一些耐心。 如果您有一个无限循环,它将永远持续下去(停止问题)。 如果它卡在某个地方,那么大多数情况下你会卡在 I/O 上或者是一个死锁。

哇! 已经有 5 个答案,没有人提出最明显和最简单的答案:

  1. 尝试找到导致挂起行为的可重现测试用例。
  2. 将日志记录添加到您的代码中。 这可以像print "**010"print "**020"等一样基本,贯穿主要区域。
  3. 运行代码。 看看它挂在哪里。 不明白为什么? 添加更多日志记录。 (即如果在 **020 和 **030 之间,则添加 **023、**025、**027 等)
  4. 转到 3。

我编写了一个模块,可以打印出在一个地方挂起时间超过 10 秒的线程。 挂线程.py

跑:

python -m pip install hanging_threads

将此添加到您的代码中:

from hanging_threads import start_monitoring
start_monitoring(seconds_frozen=10, test_interval=100)

这是一个示例输出:

--------------------    Thread 5588     --------------------
  File "C:\python33\lib\threading.py", line 844, in _exitfunc
        t.join()
  File "C:\python33\lib\threading.py", line 743, in join
        self._block.wait()
  File "C:\python33\lib\threading.py", line 184, in wait
        waiter.acquire()

当您忘记将另一个线程设置为守护进程时,这会在主线程退出时发生。

如果您的程序太大而复杂,无法使用 pdb 单步执行或使用跟踪模块打印每一行,那么您可以尝试使用我在 8 位游戏编程时代的技巧。 从 Python 2.5 开始,pdb 能够使用commands命令将代码与断点相关联。 您可以使用它来打印一条消息并继续运行:

(Pdb) commands 1
(com) print "*** Breakpoint 1 ***"
(com) continue
(com) end
(Pdb)

当断点 1 被击中时,这将打印一条消息并继续运行。 为其他几个断点定义类似的命令。

您可以使用它对您的代码进行一种二进制搜索。 在代码中的关键位置附加断点并运行它直到它挂起。 您可以从最后一条消息中知道哪个是它击中的最后一个断点。 然后您可以移动其他断点并重新运行以缩小代码中它挂起的位置。 冲洗并重复。

顺便说一句,在 8 位微型计算机(Commodore 64、Spectrum 等)上,您可以将一个值插入注册表位置以更改屏幕周围边框的颜色。 我曾经设置了几个断点来用不同的颜色来做到这一点,所以当程序运行时,它会给出一个迷幻的彩虹显示,直到它挂起,然后边框会变成单一颜色,告诉你最后一个断点是什么。 通过彩虹中每种颜色的数量,您还可以很好地了解不同代码部分的相对性能。 有时我会怀念这些新式“Windows”机器的简单性。

从 Python 3.3 开始,有一个内置的faulthandler模块:

import faulthandler
faulthandler.enable()

您也可以尝试http://code.activestate.com/recipes/576515-debugging-a-running-python-process-by-interrupting/ 只要 Python 进程没有屏蔽信号,它就应该可以工作,即使 Ctrl-C 不起作用,通常也是这种情况。

如果您的程序太复杂而无法简单地跟踪所有函数,您可以尝试运行它并手动将跟踪程序(如lptrace)附加到它。 它的工作方式有点像strace它打印你的程序调用的每个函数。 调用方法如下:

python lptrace -p $STUCK_PROGRAM_PID

请注意,lptrace 需要运行 gdb。

没有什么比好的旧pdb

import pdb
pdb.run('my_method()',globals(),locals())

然后只需按 (n) 转到下一个命令, (s) 即可进入。 有关完整参考,请参阅文档。 按照你的程序一步一步来,你可能会很快弄清楚。

防止这些挂断比调试它们更容易。

首先: for循环非常非常难以陷入循环不会终止的情况。 很难。

第二: while循环相对容易陷入循环。

第一遍是检查每个while循环,看它是否必须是一个while循环。 通常,您可以将while结构替换for ,并且您将通过重新思考循环来纠正您的问题。

如果您不能用for替换while循环,那么您只需证明while语句中的表达式必须在每次循环中更改。 这并不难证明。

  1. 查看循环中的所有条件。 称其为T

  2. 查看循环体中的所有逻辑分支。 有没有办法在不改变条件T的情况下通过循环?

    • 是的? 那是你的错误。 那条逻辑路径是错误的。

    • 不? 太好了,该循环必须终止。

多线程守护进程; 使用 pyrasite 检查正在运行的程序

我有一个多线程守护进程,有时会在数小时后卡住,有时会在数周后卡住。 通过调试器运行它是不可行的,甚至可能没有帮助,因为调试多线程或多进程程序可能很痛苦。 通过跟踪运行它可能会在它卡住之前填满千兆字节,如果不是太字节的话。 第二次后台进程出现了挂,我想马上知道它在哪里,而不需要重新启动它,加入检查代码,通过调试器来运行它,并等待几小时,几天或几周的时间,为的情况下再次挂尚未进行调查。

我被pyrasite救了出来,它允许用户连接到正在运行的 Python 进程并交互式地检查帧(受此gist启发的示例):

$ pyrasite-shell 1071  # 1071 is the Process ID (PID)
Pyrasite Shell 2.0
Connected to '/opt/pytroll/pytroll_inst/miniconda3/envs/pytroll-py38/bin/python3.8 /opt/pytroll/pytroll_inst/miniconda3/envs/pytroll-py38/bin/satpy_launcher.py -n localhost /opt/pytroll/pytroll_inst/config/trollflow2.yaml'                                                                                               
Python 3.8.6 | packaged by conda-forge | (default, Dec 26 2020, 05:05:16)
[GCC 9.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
(DistantInteractiveConsole)

>>> import sys
>>> sys._current_frames()
{139652793759488: <frame at 0x7f034b2c9040, file '<console>', line 1, code <module>>, 139653520578368: <frame at 0x7f034b232ac0, file '/opt/pytroll/pytroll_inst/miniconda3/envs/pytroll-py38/lib/python3.8/site-packages/pyresample/spherical.py', line 112, code __init__>}

第一帧没有信息; 那是我们自己的硫磷矿壳。 然而,第二帧显示当前我们的脚本卡在第 112 行的pyresample.spherical模块中。我们可以使用回溯模块来获得完整的回溯:

>>> import traceback
>>> traceback.print_stack(list(sys._current_frames().values())[1])
  File "/opt/pytroll/pytroll_inst/miniconda3/envs/pytroll-py38/bin/satpy_launcher.py", line 80, in <module>
    main()
  File "/opt/pytroll/pytroll_inst/miniconda3/envs/pytroll-py38/bin/satpy_launcher.py", line 75, in main
    run(prod_list, topics=topics, test_message=test_message,
  File "/opt/pytroll/pytroll_inst/miniconda3/envs/pytroll-py38/lib/python3.8/site-packages/trollflow2/launcher.py", line 152, in run
    proc.start()
  File "/opt/pytroll/pytroll_inst/miniconda3/envs/pytroll-py38/lib/python3.8/multiprocessing/process.py", line 121, in start
    self._popen = self._Popen(self)
  File "/opt/pytroll/pytroll_inst/miniconda3/envs/pytroll-py38/lib/python3.8/multiprocessing/context.py", line 224, in _Popen
    return _default_context.get_context().Process._Popen(process_obj)
  File "/opt/pytroll/pytroll_inst/miniconda3/envs/pytroll-py38/lib/python3.8/multiprocessing/context.py", line 277, in _Popen
    return Popen(process_obj)
  File "/opt/pytroll/pytroll_inst/miniconda3/envs/pytroll-py38/lib/python3.8/multiprocessing/popen_fork.py", line 19, in __init__
    self._launch(process_obj)
  File "/opt/pytroll/pytroll_inst/miniconda3/envs/pytroll-py38/lib/python3.8/multiprocessing/popen_fork.py", line 75, in _launch
    code = process_obj._bootstrap(parent_sentinel=child_r)
  File "/opt/pytroll/pytroll_inst/miniconda3/envs/pytroll-py38/lib/python3.8/multiprocessing/process.py", line 315, in _bootstrap
    self.run()
  File "/opt/pytroll/pytroll_inst/miniconda3/envs/pytroll-py38/lib/python3.8/multiprocessing/process.py", line 108, in run
    self._target(*self._args, **self._kwargs)
  File "/opt/pytroll/pytroll_inst/miniconda3/envs/pytroll-py38/lib/python3.8/site-packages/trollflow2/launcher.py", line 268, in process
    cwrk.pop('fun')(job, **cwrk)
  File "/opt/pytroll/pytroll_inst/miniconda3/envs/pytroll-py38/lib/python3.8/site-packages/trollflow2/plugins/__init__.py", line 403, in covers
    cov = get_scene_coverage(platform_name, start_time, end_time,
  File "/opt/pytroll/pytroll_inst/miniconda3/envs/pytroll-py38/lib/python3.8/site-packages/trollflow2/plugins/__init__.py", line 425, in get_scene_coverage
    return 100 * overpass.area_coverage(area_def)
  File "/opt/pytroll/pytroll_inst/miniconda3/envs/pytroll-py38/lib/python3.8/site-packages/trollsched/satpass.py", line 242, in area_coverage
    inter = self.boundary.contour_poly.intersection(area_boundary)
  File "/opt/pytroll/pytroll_inst/miniconda3/envs/pytroll-py38/lib/python3.8/site-packages/pyresample/spherical.py", line 494, in intersection
    return self._bool_oper(other, -1)
  File "/opt/pytroll/pytroll_inst/miniconda3/envs/pytroll-py38/lib/python3.8/site-packages/pyresample/spherical.py", line 475, in _bool_oper
    inter, edge2 = edge1.get_next_intersection(narcs2, inter)
  File "/opt/pytroll/pytroll_inst/miniconda3/envs/pytroll-py38/lib/python3.8/site-packages/pyresample/spherical.py", line 326, in get_next_intersection
    return None, None
  File "/opt/pytroll/pytroll_inst/miniconda3/envs/pytroll-py38/lib/python3.8/site-packages/pyresample/spherical.py", line 298, in intersection
    return None
  File "/opt/pytroll/pytroll_inst/miniconda3/envs/pytroll-py38/lib/python3.8/site-packages/pyresample/spherical.py", line 264, in intersections
    return (SCoordinate(lon, lat),
  File "/opt/pytroll/pytroll_inst/miniconda3/envs/pytroll-py38/lib/python3.8/site-packages/pyresample/spherical.py", line 62, in cross2cart
    return res
  File "/opt/pytroll/pytroll_inst/miniconda3/envs/pytroll-py38/lib/python3.8/site-packages/pyresample/spherical.py", line 112, in __init__
    self.cart = np.array(cart)

我们可以使用 Python 内省的所有功能来检查堆栈,以帮助我们重建卡住的情况。

我自己没有使用过,但我听说Eric IDE很好并且有一个很好的调试器。 这也是我所知道的唯一一个带有 Python 调试器的 IDE

如果您的程序有多个线程,它可能会忽略 ctrl-c,因为一个线程连接到 ctrl-c 处理程序,但实时(失控?)线程对其充耳不闻。 CPython 中的 GIL(全局解释器锁)意味着通常任何时候实际上只有一个线程可以运行。 我想我用这个解决了我的(也许)类似的问题

i = 0
for t in threading.enumerate():
    if i != 0:# and t.getName() != 'Thread-1':
        print t.getName()
        t._Thread__stop()
    i += 1

一旦你知道线程的名称; 开始重新执行你的脚本并过滤它们,而不是阻止它们被中止。 i=0 条件防止主线程被中止。

我建议浏览并命名所有线程; 如:Thread(target=self.log_sequence_status, name='log status')

这段代码应该放在启动失控进程的主程序的末尾

哇 ! 似乎您一次添加了这么多代码而没有对其进行测试,以至于您无法说出在程序开始挂起之前添加了哪些代码......(最可能的问题原因)。

说真的,您应该分步编写代码并单独测试每个步骤(最好是 TDD)。

对于您发现正在运行的 python 代码和 ctrl-c 不起作用的确切问题,我将尝试一个原始猜测:您是否使用了一些except:模糊地捕获所有异常。 如果您在循环中这样做(并在管理异常后继续循环),这很可能是 ctrl-c 不起作用的原因:它被此异常捕获。 更改为except Exception:并且不应再捕获它(还有其他可能 ctrl+c 不像另一个海报建议的线程管理那样工作,但我相信上述原因更有可能)。

异常键盘中断

Raised when the user hits the interrupt key (normally Control-C or Delete).

在执行期间,会定期检查中断。 当内置函数 input() 或 raw_input() 等待输入时输入的中断也会引发此异常。 异常继承自 BaseException,以免被捕获 Exception 的代码意外捕获,从而阻止解释器退出。

 Changed in version 2.5: Changed to inherit from BaseException.

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM