簡體   English   中英

多處理+異步的SystemExit死鎖

[英]SystemExit deadlock with multiprocessing + asyncio

在 asyncio 任務中引發SystemExit時,以下代碼會死鎖而不是退出。

import asyncio
import multiprocessing as mp

def worker_main(pipe):
    try:
        print("Worker started")
        pipe.recv()
    finally:
        print("Worker exiting.")

async def main():
    _, other_end = mp.Pipe()
    worker = mp.Process(target=worker_main, args=(other_end,))
    worker.daemon = False # (default), True causes multiprocessing explicitly to terminate worker
    worker.start()
    await asyncio.sleep(2)
    print("Main task raising SystemExit")
    raise SystemExit

if __name__ == "__main__":
    asyncio.get_event_loop().run_until_complete(main())

它僅打印以下內容然后掛起:

Worker started
Main task raising SystemExit

如果我然后按 Ctrl+CI 得到一個回溯,表明它正在等待 multiprocessing.util._exit_function() 中的p.join() multiprocessing.util._exit_function()

這是 Python 3.9.5 上的 Windows 10。

從表面上看,原因似乎如下:

當在異步任務中引發SystemExit時,似乎所有任務都已結束,然后SystemExit在事件循環之外重新引發。 多處理庫已使用atexit.register()注冊multiprocessing.util._exit_function() ) ,並在主進程退出時調用它。 這將在工作進程上執行p.join() 至關重要的是,這發生pipe 關閉之前,因此它與等待 pipe 關閉 ( EOFError ) 的工作人員死鎖,而主進程等待工作人員退出。

解決方案/解決方法似乎是讓工作人員成為守護進程,以便_exit_function()將在p.join()之前明確終止它,從而打破僵局。 唯一的問題是它會阻止工作人員在退出之前進行任何清理工作。

在非 asyncio 應用程序中不會出現同樣的問題,我不確定為什么會有所不同。 如果應用程序是非異步的,那么當主進程退出 pipe 到 worker 時,worker 將按預期退出並出現EOFError 我還確認如果 asyncio 任務被允許正常退出,但在run_until_complete()返回后有一個raise SystemExit那么它的行為與非 asyncio 情況一樣 - 即它正確退出。

這是 Python 中的一個錯誤,還是它應該以這種方式運行?

Python3.10.0 + Win10 具有相同的行為。

這是 Python 中的一個錯誤,還是預期會這樣?

好問題。 在我看來,這不是 Python 中的錯誤。您有一個具有單個任務和單個線程的 asyncio 程序。 一旦啟動事件循環,主線程中的所有代碼都在事件循環中運行。 當它執行p.join()時,它是一個阻塞調用。 由於沒有第二個線程可以導致解除阻塞,因此程序會在此時掛起。

據我了解,asyncio 程序中的每個任務都會在(可能)傳播異常之前處理自己的異常,通常傳播到 asyncio.run() 調用。 (至少這是我的經驗,否則很難看出它會怎樣。)所以異常處理環境在同步和異步程序之間是不同的。

第二點:Python 不可能知道如何優雅地終止非守護進程。 如果您將進程指定為守護進程,您實際上是在告訴 Python 該進程可以安全終止。 但是,正如您所指出的那樣,這將繞過所需的任何清理工作。

由於您有一個非守護進程,因此由您的代碼執行清理。

第三點:如果你更換這一行:

raise SystemExit

有了這個:

raise Exception

出現完全相同的問題,並且出於相同的原因。 該程序不會退出,因為有一個非守護進程正在運行。 問題不僅僅是從 SystemExit 中清理,而是從任何異常中清理。 如果你解決了那個問題,你的問題就沒有實際意義了。

您可以在主 function 中顯式捕獲 SystemExit 和 Exception,如下所示:

import asyncio
import multiprocessing as mp

def worker_main(pipe):
    try:
        print("Worker started")
        pipe.recv()
    finally:
        print("Worker exiting.")

async def main():
    my_end, other_end = mp.Pipe()
    worker = mp.Process(target=worker_main, args=(other_end,))
    try:
        worker.daemon = False 
        worker.start()
        await asyncio.sleep(2)
        print("Main task raising SystemExit")
        raise SystemExit
        # raise Exception
    except (SystemExit, Exception):
        my_end.send("Bye")
        worker.join()
        raise

if __name__ == "__main__":
    asyncio.run(main())

您可以通過注釋掉一個或另一個來了解 SystemExit 和 Exception 之間的區別。 兩人優雅地退出。 SystemExit 不會將回溯打印到控制台; 例外。

暫無
暫無

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

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