簡體   English   中英

Python多處理進程以靜默方式崩潰

[英]Python multiprocessing Process crashes silently

我正在使用Python 2.7.3。 我使用子類化multiprocessing.Process對象並行化了一些代碼。 如果我的子類Process對象中的代碼沒有錯誤,那么一切運行正常。 但是如果我的子類Process對象中的代碼有錯誤,它們顯然會無聲地崩潰(沒有堆棧跟蹤打印到父shell),CPU使用率將降至零。 父代碼永遠不會崩潰,給人的印象是執行只是掛起。 同時,很難追蹤代碼中的錯誤,因為沒有給出關於錯誤位置的指示。

我在stackoverflow上找不到任何其他問題來處理同樣的問題。

我想子類化的Process對象似乎是靜默崩潰的,因為它們無法向父shell發送錯誤消息,但我想知道我能做些什么,這樣我至少可以更高效地調試(以及其他我的代碼的用戶可以告訴我他們何時遇到問題)。

編輯:我的實際代碼太復雜,但是一個帶有錯誤的子類Process對象的簡單示例將是這樣的:

from multiprocessing import Process, Queue

class Worker(Process):

    def __init__(self, inputQueue, outputQueue):

        super(Worker, self).__init__()

        self.inputQueue = inputQueue
        self.outputQueue = outputQueue

    def run(self):

        for i in iter(self.inputQueue.get, 'STOP'):

            # (code that does stuff)

            1 / 0 # Dumb error

            # (more code that does stuff)

            self.outputQueue.put(result)

你真正想要的是將異常傳遞給父進程的一些方法,對吧? 然后你可以隨意處理它們。

如果使用concurrent.futures.ProcessPoolExecutor ,則這是自動的。 如果你使用multiprocessing.Pool ,它是微不足道的。 如果你使用顯式的ProcessQueue ,你必須做一些工作,但它不是那么多。

例如:

def run(self):
    try:
        for i in iter(self.inputQueue.get, 'STOP'):
            # (code that does stuff)
            1 / 0 # Dumb error
            # (more code that does stuff)
            self.outputQueue.put(result)
    except Exception as e:
        self.outputQueue.put(e)

然后,您的調用代碼可以像其他任何東西一樣從隊列中讀取Exception 而不是這個:

yield outq.pop()

做這個:

result = outq.pop()
if isinstance(result, Exception):
    raise result
yield result

(我不知道你的實際父進程隊列讀取代碼是做什么的,因為你的最小樣本只是忽略了隊列。但希望這解釋了這個想法,即使你的真實代碼實際上並沒有像這樣工作。)

這假定您要中止任何未處理的異常,使其run 如果你想傳回異常並繼續下一個i in iter ,只需將try移動到for ,而不是它周圍。

這也假設Exception不是有效值。 如果這是一個問題,最簡單的解決方案就是推送(result, exception)元組:

def run(self):
    try:
        for i in iter(self.inputQueue.get, 'STOP'):
            # (code that does stuff)
            1 / 0 # Dumb error
            # (more code that does stuff)
            self.outputQueue.put((result, None))
    except Exception as e:
        self.outputQueue.put((None, e))

然后,您的彈出代碼執行此操作:

result, exception = outq.pop()
if exception:
    raise exception
yield result

您可能會注意到這與node.js回調樣式類似,您將(err, result)傳遞給每個回調。 是的,這很煩人,而且你會以這種方式弄亂代碼。 但除了包裝器外,你實際上並沒有使用它; 從隊列中獲取值或在run調用的所有“應用程序級”代碼只能看到正常的返回/收益和引發的異常。

您甚至可能想要考慮使用concurrent.futures (或者按原樣使用該類)的規范構建Future ,即使您正在排隊並手動執行。 它並不難,它為您提供了一個非常好的API,特別是對於調試。

最后,值得注意的是,使用執行程序/池設計可以使圍繞工作程序和隊列構建的大多數代碼變得更加簡單,即使您完全確定每個隊列只需要一個工作程序。 只需廢棄所有樣板,然后將Worker.run方法中的循環轉換為函數(只return s或正常raise s,而不是追加到隊列中)。 在呼叫方面,再次廢棄所有樣板,只需使用其參數submitmap作業功能。

您的整個示例可以簡化為:

def job(i):
    # (code that does stuff)
    1 / 0 # Dumb error
    # (more code that does stuff)
    return result

with concurrent.futures.ProcessPoolExecutor(max_workers=1) as executor:
    results = executor.map(job, range(10))

並且它會自動正確處理異常。


正如您在評論中提到的,異常的回溯不會追溯到子進程; 它只能進行手動raise result調用(或者,如果您使用的是池或執行程序,那么池或執行程序的內容)。

原因是multiprocessing.Queue建立在pickle之上,而pickle異常並沒有腌制他們的追溯。 原因是你不能腌制回溯。 原因是回溯充滿了對本地執行上下文的引用,因此使它們在另一個進程中工作將非常困難。

那么......你能做些什么呢? 不要去尋找一個完全通用的解決方案。 相反,想想你真正需要什么。 90%的時間,你想要的是“記錄異常,使用回溯,並繼續”或“使用traceback打印異常,到stderrexit(1)就像默認的unhandled-exception handler”。 對於其中任何一個,您根本不需要傳遞異常; 只需在子端進行格式化並傳遞一個字符串。 如果你確實需要一些更加花哨的東西,請確切地計算出你需要的東西,並傳遞足夠的信息來手動將它們組合在一起。 如果您不知道如何格式化回溯和異常,請參閱traceback模塊。 這很簡單。 這意味着你根本不需要進入泡菜機械。 (不,這是非常難copyreg一個皮克勒或寫有一個holder類__reduce__方法或任何東西,但如果你不需要,為什么學各是什么?)

我建議使用這種解決方法來顯示流程的異常

from multiprocessing import Process
import traceback


run_old = Process.run

def run_new(*args, **kwargs):
    try:
        run_old(*args, **kwargs)
    except (KeyboardInterrupt, SystemExit):
        raise
    except:
        traceback.print_exc(file=sys.stdout)

Process.run = run_new

這不是一個答案,只是一個擴展的評論。 請運行此程序,告訴我們您獲得的輸出(如果有):

from multiprocessing import Process, Queue

class Worker(Process):

    def __init__(self, inputQueue, outputQueue):

        super(Worker, self).__init__()

        self.inputQueue = inputQueue
        self.outputQueue = outputQueue

    def run(self):

        for i in iter(self.inputQueue.get, 'STOP'):

            # (code that does stuff)

            1 / 0 # Dumb error

            # (more code that does stuff)

            self.outputQueue.put(result)

if __name__ == '__main__':
    inq, outq = Queue(), Queue()
    inq.put(1)
    inq.put('STOP')
    w = Worker(inq, outq)
    w.start()

我明白了:

% test.py
Process Worker-1:
Traceback (most recent call last):
  File "/usr/lib/python2.7/multiprocessing/process.py", line 258, in _bootstrap
    self.run()
  File "/home/unutbu/pybin/test.py", line 21, in run
    1 / 0 # Dumb error
ZeroDivisionError: integer division or modulo by zero

我很驚訝(如果)你什么都沒得到。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM