最近處理一個線程中的函數超時問題.
函數里面有一個地方可能會卡死,我們需要去判斷這個是不是卡死了,並做出相應的應對方案.
最開始想的是在函數上增加一個裝飾器,使其在超時時拋出異常,然后在其他地方捕獲這個異常,並處理.
查詢了一些前人的方案,寫出的結果有兩種.
方案一:使用threading的timer定時器,代碼如下:
from threading import timer
def time_limit(interval): def wraps(func): def time_out(): raise RuntimeError() def deco(*args, **kwargs): timer = Timer(interval, time_out) timer.start() res = func(*args, **kwargs) timer.cancel() return res return deco return wraps
這個程序,在很多網頁上看到了,使用方式,在需要監控的函數上寫@time_limit(5),即可定時5秒報錯.經過測試,覺得沒什么用.
粗略看來,運行一下確實在程序超過interval規定的時間后拋出了RuntimeError.但是由於timer是另開的一個線程,所以這個異常別人獲取不到,只有在本線程里處理才有用.
而且卡死的函數也不會停下來.
這種處理方式僅僅適用於一些函數與定時並不密切的時候,只是在函數超時時需要做一些動作的情況.
方案二:使用signal信號量機制,代碼如下:
import signal
def time_limit(interval): def wraps(func): def handler(): raise RuntimeError() def deco(*args, **kwargs): signal.signal(signal.SIGALRM, handler) signal.alarm(interval) res = func(*args, **kwargs) signal.alarm(0) return res return deco return wraps
可以看出來,代碼基本一樣.但是原理大不相同.運行的結果也不一樣,這段代碼裝飾需要定時的函數,在函數運行超時之后會拋出異常,停止程序.
一般情況下,這個裝飾器就夠用了.與之相同的方案,還有python的timeout模塊,
可以直接pip install timeout,然后from timeout import timeout,在需要的函數前@timeout(n)即可.
這個方案已經很不錯了,然而在實際應用中,由於是多線程,而signal不能在子線程中使用,所以否認了這個方案.
比較簡單的裝飾器想法否定掉了,就想了一些其他的辦法.
1.定時檢測線程是否alive----------卡死的線程只是不動,還是alive的
2.由於函數是生產者,可以通過計算時間,一定時間沒有新的產出,則判定這個函數卡死,然后殺掉這個線程,重開一個----------python不支持殺掉線程
3.在線程里面增加標志位,檢測標志位改變則退出循環----------天真的想法
4.改變線程為可接受stop的線程----------卡住不動收不到信號的,除非是進程
提出一些方案發現可行性都很低,最后也沒有完全解決卡死的問題.
雖然這一個線程卡死並不會影響整個程序往下走,但是不生產,又占據資源不合理.
覺得否認了這些方案以后,最干脆的辦法是檢測到這個卡死之后,手動或自動殺掉其父進程.重啟服務.
還有較不優雅的辦法,由於卡死的情況不會很多,就記錄下來,然后新開一個線程替代它的工作,直到記錄隊列滿了,給一個信號要求重啟服務.
重啟服務影響較大,不建議隨便重啟.
總的說下來,還是沒有較好的方案解決線程卡死的問題,畢竟函數卡死了就是卡死了,沒法走下去,而且在線程里面又沒法外界停下來.
先做一個記錄吧!在繼續學習的路上,希望有好的辦法.