簡體   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