簡體   English   中英

Python 多處理,子進程在發送前引發異常后,父進程在接收時掛起

[英]Python multiprocessing, parent process hangs at recv after child process raises exception before send

Python 絕對是並行處理最差的語言之一。

只是讓父進程在子進程失敗時拋出錯誤。 相反,父進程在recv()方法處掛起。

子進程中的一些代碼:

try:
    condition.acquire()
    x = 0/0
    pipeConn2.send('test data')
    condition.notify_all()
    condition.release()
except:
    pipeConn2.close() # expect this to raise exception in parent process per docs
    condition.notify_all()
    condition.release()

父進程中的一些代碼:

try:
    condition0.acquire()
    condition0.wait()
    pipe0Conn1.recv() # hangs here instead of raising exception
except:
    # handle exception

首先,你有一個競爭條件。 即使您消除了除零異常並且從不關閉連接,如果子進程首先獲取與底層condition實例關聯的隱式鎖,則主進程中對recv的調用無論如何都會掛起(我給你懷疑conditioncondition0確實引用了相同的實例。

問題是,如果您的子進程是第一個獲得鎖的,它將發送其數據,進行通知,釋放鎖並終止。 然后主進程將能夠第一次獲取鎖,然后調用wait 但是當它調用wait時,它會釋放鎖並隱式等待已經發生的通知,並將無限期地掛起wait 您可以通過注釋掉語句x = 0/0並插入import time; time.sleep(.5) import time; time.sleep(.5)作為主進程的try塊中的第一條語句,以確保子進程首先獲取條件鎖。

只是為了解決這個問題,我使用一個Event實例來暫停子進程,直到主進程向它發出信號,它已經獲得了條件鎖。

我已經驗證,當子進程關閉連接時,主進程掛起似乎確實存在問題。 但是,當我用多線程代替多處理時,這確實可以正常工作。 所以 Python 中似乎存在一個錯誤。 我已經在 Windows 和 Python 3.8.5 和 Linux 和 ZA7F5F35426B923784711Z 上測試了這個。

這是一個最小的、可重現的示例:

USE_THREADING = False

from multiprocessing import Pipe
if USE_THREADING:
    from threading import Thread as SubTask, Condition, Event
else:
    from multiprocessing import Process as SubTask, Condition, Event

def foo(condition, pipeConn2, acquired_event):
    try:
        acquired_event.wait()
        condition.acquire()
        x = 0/0
        pipeConn2.send('test data')
    except:
        pipeConn2.close() # expect this to raise exception in parent process per docs
    finally:
        condition.notify_all()
        condition.release()

if __name__ == '__main__':
    condition = Condition()
    pipeConn1, pipeConn2 = Pipe()
    # Ensure that our main process/thread is first to acquire the condition:
    acquired_event = Event()
    p = SubTask(target=foo, args=(condition, pipeConn2, acquired_event))
    p.start()
    try:
        condition.acquire()
        # Now allow the subprocess/subthread to proceed to try to acquire the lock:
        acquired_event.set()
        condition.wait()
        print('Receiving data ...')
        data = pipeConn1.recv()
        print(f"Data received: '{data}'")
    except Exception as e:
        print('Exception:', type(e))
    p.join()

印刷:

Receiving data ...

順便說一句,你的例子並不是一個特別現實的例子。 Pipe本質上是一個單一的消費者/單一的生產者通信工具,除非你在它上面構建了很多“腳手架”,然后你還不如只使用其中一個Queue變體。 實際上,一個進程或線程只會在連接上進行阻塞接收,您的生產者可以編寫一個特殊的標記object (通常None足夠的)來表示這是“文件結尾”。 確實沒有必要對連接進行顯式關閉; 當它被垃圾收集時它將被關閉。

我遇到了完全相同的問題,即Parent Pipe Connection正在無限期地等待。 我沒有使用Lock ,而是使用了Parent Pipe Connection. Poll Parent Pipe Connection. Poll function 並返回 boolean 值,具體取決於是否有任何接收。 一些例子:-

沒有Poll -

try:
    pipe0Conn1.recv() # Waits indefinetly (until it recieves)
except EOFError as e:
    # handling not recieved
except:
    # some other exception

沒有Poll -

try:
    if pipe0Conn1.poll() is True:
        pipe0Conn1.recv() # Doesn't wait indefinetly (immediate recieve)
    else:
        # Some other code for handling nothing recieved
except EOFError as e:
    # handling not recieved
except:
    # some other exception

PS:這是我在stackoverflow上的第一個答案,對於任何語法、格式、代碼錯誤,我感到非常抱歉。 如果您願意,請評論任何反饋。

暫無
暫無

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

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