2011-10-10 131 views
24

注意。我見過Log output of multiprocessing.Process - 不幸的是,它沒有回答這個問題。Python多處理:我如何可靠地重定向來自子進程的標準輸出?

我正在通過多處理創建一個子進程(在Windows上)。我想所有的子進程的stdout和stderr輸出都被重定向到一個日誌文件,而不是出現在控制檯上。我所看到的唯一建議是讓子進程將sys.stdout設置爲文件。但是,由於Windows上stdout重定向的行爲,這不會有效地重定向所有stdout輸出。

爲了說明問題,建立一個Windows DLL用下面的代碼

#include <iostream> 

extern "C" 
{ 
    __declspec(dllexport) void writeToStdOut() 
    { 
     std::cout << "Writing to STDOUT from test DLL" << std::endl; 
    } 
} 

然後創建並運行類似以下,其中進口該DLL並調用函數的Python腳本:

from ctypes import * 
import sys 

print 
print "Writing to STDOUT from python, before redirect" 
print 
sys.stdout = open("stdout_redirect_log.txt", "w") 
print "Writing to STDOUT from python, after redirect" 

testdll = CDLL("Release/stdout_test.dll") 
testdll.writeToStdOut() 

爲了看到和我一樣的行爲,可能需要針對不同於C++運行時的C運行時構建DLL。就我而言,蟒蛇是建立與Visual Studio 2010,但我的DLL是建立與VS 2005

我看到的行爲是控制檯顯示:

> stdout_test.py 

Writing to STDOUT from python, before redirect 

Writing to STDOUT from test DLL 

雖然文件stdout_redirect_log.txt結束包含:

Writing to STDOUT from python, after redirect 

換句話說,設置sys.stdout無法重定向由該DLL生成的stdout輸出。考慮到Windows中用於標準輸出重定向的底層API的性質,這並不令人驚訝。我以前在native/C++級別遇到過這個問題,並且從來沒有找到一種方法可以在進程內可靠地重定向stdout。它必須在外部完成。

這實際上是我啓動子進程的原因 - 這樣我可以將外部連接到它的管道,從而保證我攔截它的所有輸出。我可以通過使用pywin32手動啓動該過程來完成此任務,但我非常希望能夠使用多處理功能,特別是通過多處理Pipe對象與子進程進行通信的能力,以獲得進度更新。問題是,是否有任何方法可以使用多處理技術爲其IPC工廠將子級的所有stdout和stderr輸出可靠地重定向到文件。

UPDATE:看爲multiprocessing.Processs的源代碼,它具有一個靜止構件,_popen,它看起來像它可以被用於覆蓋用於創建過程的類。如果設置爲none(默認值),它採用了multiprocessing.forking._Popen,但它說

multiprocessing.Process._Popen = MyPopenClass 

我可以覆蓋進程創建的模樣。但是,儘管我可以從multiprocessing.forking.Popen中得到這個結果,但是看起來我必須將一堆內部的東西複製到我的實現中,這聽起來很片面,而且不是很有前途。如果這是唯一的選擇,我想我可能會用pywin32來手動完成整個事情。

+0

您可以使用Win32 API來啓動子進程,還是必須使用現有的Python庫來完成? –

+0

是的,我在問題中提到「我可以通過使用pywin32手動啓動該過程來完成此操作」。放棄更高級別,獨立於平臺的多處理模塊似乎是一種恥辱,因爲似乎缺少一些功能缺失的功能 - 爲孩子指定stdin/stdout句柄的能力。 – Tom

+1

我正在採用的方法(除非有人提出了更好的選擇)是通過子進程模塊啓動進程,並將stdin/stdout重定向到文件,並使用本機Windows命名管道進行進程通信。 – Tom

回答

7

您建議的解決方案是一個很好的解決方案:手動創建您的流程,以便您可以顯式訪問其stdout/stderr文件句柄。然後,您可以創建一個套接字與子進程進行通信,並使用該套接字上的multiprocessing.connection(multiprocessing.Pipe創建相同類型的連接對象,所以這應該會給您所有相同的IPC功能)。

這是一個雙文件示例。

master.py:

import multiprocessing.connection 
import subprocess 
import socket 
import sys, os 

## Listen for connection from remote process (and find free port number) 
port = 10000 
while True: 
    try: 
     l = multiprocessing.connection.Listener(('localhost', int(port)), authkey="secret") 
     break 
    except socket.error as ex: 
     if ex.errno != 98: 
      raise 
     port += 1 ## if errno==98, then port is not available. 

proc = subprocess.Popen((sys.executable, "subproc.py", str(port)), stdout=subprocess.PIPE, stderr=subprocess.PIPE) 

## open connection for remote process 
conn = l.accept() 
conn.send([1, "asd", None]) 
print(proc.stdout.readline()) 

subproc.py:

import multiprocessing.connection 
import subprocess 
import sys, os, time 

port = int(sys.argv[1]) 
conn = multiprocessing.connection.Client(('localhost', port), authkey="secret") 

while True: 
    try: 
     obj = conn.recv() 
     print("received: %s\n" % str(obj)) 
     sys.stdout.flush() 
    except EOFError: ## connection closed 
     break 

您可能還希望看到的第一個答案this question得到非阻塞從子進程讀取。

1

我不認爲你有比在你的評論中提到的將子過程重定向到文件更好的選擇。

控制檯stdin/out/err在windows中的工作方式是每個進程在它的出生時已經定義了它的std handles。您可以使用SetStdHandle更改它們。當你修改python的sys.stdout時,你只能修改python打印出東西的地方,而不是其他DLL打印東西的地方。你的DLL中的CRT的一部分使用GetStdHandle來找出打印出來的位置。如果你願意,你可以在你的DLL的windows API或你的Python腳本中用pywin32做你想要的任何管道。儘管我確實認爲subprocess會更簡單。

0

我假設我離開了基地並錯過了一些東西,但是當我讀到你的問題時,想到這裏是值得思考的。

如果你可以攔截所有的stdout和stderr(我從你的問題中得到了這種印象),那麼爲什麼不在你的每個進程中添加或包裝捕獲功能呢?然後將通過隊列捕獲的內容發送給消費者,消費者可以對所有輸出做任何你想做的事情?

相關問題