2011-10-10 12 views
24

NB. He visto Log output of multiprocessing.Process - lamentablemente, no responde esta pregunta.Multiprocesamiento Python: ¿Cómo puedo redireccionar FIABLY stdout de un proceso hijo?

Estoy creando un proceso secundario (en Windows) a través de multiprocesamiento. Quiero de la salida stdout y stderr del proceso secundario para ser redirigido a un archivo de registro, en lugar de aparecer en la consola. La única sugerencia que he visto es que el proceso secundario establezca sys.stdout en un archivo. Sin embargo, esto no redirige efectivamente todos los resultados de stdout, debido al comportamiento de la redirección stdout en Windows.

Para ilustrar el problema, construir un archivo DLL de Windows con el siguiente código

#include <iostream> 

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

A continuación, crear y ejecutar un script en Python, como la siguiente, que importa esta DLL y llama a la función:

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() 

Para ver el mismo comportamiento que yo, probablemente sea necesario que la DLL se construya en un tiempo de ejecución de C diferente del que utiliza Python. En mi caso, pitón se construye con Visual Studio 2010, pero mi DLL está construido con VS 2005.

El comportamiento que veo es que la consola muestra:

> stdout_test.py 

Writing to STDOUT from python, before redirect 

Writing to STDOUT from test DLL 

Mientras que el archivo termina stdout_redirect_log.txt que contiene:

Writing to STDOUT from python, after redirect 

En otras palabras, el establecimiento de sys.stdout no pudo redirigir la salida stdout generada por el DLL. Esto no es sorprendente dada la naturaleza de las API subyacentes para la redirección stdout en Windows. He encontrado este problema en el nivel nativo/C++ antes y nunca he encontrado una manera de redirigir de manera confiable stdout desde dentro de un proceso. Tiene que hacerse externamente.

Esta es realmente la razón por la que estoy iniciando un proceso secundario, es para poder conectarme externamente a sus tuberías y así garantizar que estoy interceptando toda su salida. Definitivamente puedo hacer esto iniciando el proceso manualmente con pywin32, pero me gustaría mucho poder utilizar las funciones de multiprocesamiento, en particular la capacidad de comunicarme con el proceso hijo a través de un objeto Pipe de multiprocesamiento, para poder avanzar actualizaciones. La pregunta es si hay alguna forma de utilizar el multiprocesamiento para sus instalaciones IPC y para redirigir de manera confiable toda la salida stdout y stderr del niño a un archivo.

ACTUALIZACIÓN: Si examina el código fuente para multiprocessing.Processs, que tiene un miembro estático, _popen, que parece que se puede utilizar para sustituir la clase utilizada para crear el proceso. Si se establece en Ninguno (predeterminado), se utiliza un multiprocessing.forking._Popen, pero parece que al decir

multiprocessing.Process._Popen = MyPopenClass 

que podría anular el proceso de creación. Sin embargo, aunque podría derivar esto de multiprocessing.forking._Popen, parece que tendría que copiar un montón de cosas internas en mi implementación, que suena escamosa y no muy a prueba de futuro. Si esa es la única opción, creo que probablemente me gustaría hacer todo manualmente con pywin32.

+0

¿Se puede usar la API de Win32 para iniciar el subproceso, o tiene que hacerse con las bibliotecas de Python existentes? –

+0

Sí, mencioné en la pregunta que "definitivamente puedo hacer esto iniciando el proceso manualmente con pywin32". Parecía una pena abandonar el módulo de multiprocesamiento independiente de la plataforma de nivel superior debido a lo que parece ser una pequeña parte de la funcionalidad que falta: la capacidad de especificar los controladores stdin/stdout para el niño. – Tom

+1

El enfoque que estoy tomando (a menos que a alguien se le ocurra una mejor alternativa) es iniciar el proceso a través del módulo de subproceso, con stdin/stdout redirigido a un archivo y usar un canal nativo de Windows para la comunicación de progreso. – Tom

Respuesta

7

La solución que sugiere es buena: cree sus procesos manualmente para que tenga acceso explícito a sus manejadores de archivos stdout/stderr. A continuación, puede crear un socket para comunicarse con el subproceso y usar multiprocesamiento.conexión sobre ese socket (multiprocesamiento.Pipe crea el mismo tipo de objeto de conexión, por lo que debería proporcionarle la misma funcionalidad de IPC).

Aquí hay un ejemplo de dos archivos.

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 

También puede ver la primera respuesta a this question conseguir sin bloqueo lee desde el subproceso .

1

No creo que tenga una mejor opción que redirigir un subproceso a un archivo como mencionó en su comentario.

La manera en que las consolas stdin/out/err funcionan en Windows es cada proceso cuando nace tiene su std handles definido. Puede cambiarlos con SetStdHandle. Cuando modificas el sys.stdout de python, solo modificas dónde python imprime cosas, no donde otras DLL están imprimiendo cosas. Parte de la CRT en su DLL está utilizando GetStdHandle para averiguar dónde imprimir. Si lo desea, puede hacer cualquier canalización que desee en la API de Windows en su archivo DLL o en su secuencia de comandos python con pywin32. Aunque creo que será más simple con subprocess.

0

Supongo que estoy fuera de la base y me falta algo, pero por lo que vale aquí es lo que me vino a la mente cuando leí tu pregunta.

Si puede interceptar todos los stdout y stderr (obtuve esa impresión de su pregunta), ¿por qué no agregar o ajustar esa funcionalidad de captura en cada uno de sus procesos? Luego, envíe lo que se captura a través de una cola a un consumidor que puede hacer lo que quiera con todas las salidas.

Cuestiones relacionadas