2011-05-18 46 views
4

Twisted文檔使我相信,在相同的應用程序中結合諸如reactor.spawnProcess()threads.deferToThread()等技術是可以的,以便反應器能夠在封面下處理這個問題。在實際嘗試之後,我發現我的應用程序發生死鎖。自己使用多個線程,或者自己使用子進程,一切都很好。Twisted:一起使用多個線程和進程

縱觀反應堆來源,我發現SelectReactor.spawnProcess()方法只是簡單地調用os.fork()而不考慮可能正在運行的多個線程。這解釋了死鎖,因爲從os.fork()的調用開始,您將擁有兩個進程,其中有多個併發線程正在運行並執行誰知道同一文件描述符是什麼。

我對SO的問題是,解決這個問題的最佳策略是什麼?

我想到的是子類SelectReactor,因此它是一個單例,並且只在實例化時立即調用os.fork()一次。子進程將在後臺運行,並充當父進程的服務器(使用管道上的對象序列化來回傳遞)。父級繼續運行該應用程序,並可根據需要使用線程。調用父母中的spawnProcess()將委派給子進程,子進程將保證只有一個線程正在運行,因此可以安全地調用os.fork()

有沒有人做過這個?有更快的方法嗎?

回答

2

一段時間後返回到這個問題,我發現,如果我這樣做:

reactor.callFromThread(reactor.spawnProcess, *spawnargs)

,而不是這樣的:

reactor.spawnProcess(*spawnargs)

然後問題消失在我的小測試用例。 Twisted文檔「Using Processes」中有一句話讓我嘗試這樣做:「Twisted中的大多數代碼都不是線程安全的,例如,從協議將數據寫入傳輸程序不是線程安全的。」

我懷疑讓 - 保羅提到的其他人有這個問題可能會犯一個類似的錯誤。責任在於應用程序執行該反應堆,而其他API調用正在正確的線程內進行。顯然,除非常狹窄的例外,「正確的線程」幾乎總是主要的反應堆線程。

4

解決此問題的最佳策略是什麼?

File a ticket(也許registering之後)所描述的問題,最好用可再現的測試情況下(最大精度)。然後可以討論一下實現它的最佳方式(或多種方式 - 不同的平臺可能需要不同的解決方案)。

之前提出了立即創建子進程以幫助進一步子進程創建的想法,以解決圍繞子進程收穫的性能問題。如果這種方法現在解決了兩個問題,它開始看起來更有吸引力。這種方法的一個潛在困難是spawnProcess同步返回一個提供孩子PID並允許發送信號的對象。如果有一箇中間過程,那麼這是一個稍微多一點的工作,因爲在spawnProcess返回之前,PID將需要傳回主進程。類似的挑戰將會支持childFDs的論點,因爲它將不再可能僅僅繼承子進程中的文件描述符。

在調用os.fork之前,可能需要調用sys.setcheckinterval一個非常大的數字,然後在父進程中恢復原始檢查時間間隔(可能稍微有些駭人,但實施難度可能更少) 。這應該足以避免在進程中發生任何線程切換,直到os.execvpe發生,從而破壞所有額外的線程。這是不完全正確的,因爲它會使某些資源(如互斥鎖和條件)處於不良狀態,但使用deferToThread這些並不常見,因此這可能不會影響您的情況。

+1

謝謝。我沒有想過childFD。一些有dup2的雜技可能就足夠了,但這意味着更多的工作。如果我能把它弄明白的話,我會把它縮小到我能夠做到的最小的測試案例,並從事扭曲矩陣。我會傳遞setcheckinterval策略,但其他人可能會接受。 – wberry 2011-05-19 15:46:58

2

讓 - 保羅在他的回答中給出的建議是好的,但是這個應該工作(並且在大多數情況下)。

首先,Twisted也使用線程進行主機名解析,而且我確定在Twisted進程中也使用了子進程來建立客戶端連接。所以這可以在實踐中起作用。

其次,fork()不會在子進程中創建多個線程。 According to the standard describing fork()

一個進程應該創建一個單線程。如果一個多線程進程調用fork(),新工藝應包含調用線程的一個副本...

現在,這並不是說,有沒有潛在多線程問題與spawnProcess;標準還表示:

...以避免錯誤,子進程可能僅執行異步信號安全操作,直到時間的執行函數一個叫...

我不認爲有什麼可以確保只使用異步信號安全的操作。

因此,請更具體地說明您的具體問題,因爲它不是線程被克隆的子進程。

+1

謝謝你的鏈接;我不知道只有線程調用fork()被複制。然而,我不相信我所做的「應該」有兩個原因。首先,我聽說GNU/Linux並不符合Pthreads的標準,所以標準中的一些語義可能不適用於我。其次,我不知道Python解釋器在覆蓋下可能會做什麼,當只有一個線程在子進程中存活時可能導致我的死鎖。我需要將其減少到我能做到的最小測試案例。 – wberry 2011-05-19 15:40:41

+1

'fork()'-only-producing-one-thread是pthreads的一個非常重要的部分,我非常確定Linux符合SUS的這一部分。但是,你是絕對正確的:如果沒有一個明確的,小的測試用例來證明問題很難說清楚。 – Glyph 2011-05-19 18:28:19

1

Linux上的fork()肯定會讓子進程只留下一個線程。

我假設您知道,在Twisted中使用線程時,線程允許調用的唯一Twisted API是callFromThread?所有其他Twisted API只能從主反應器線程調用。