Python multiprocessing: Comment puis-je rediriger de manière fiable stdout à partir d'un processus enfant?

NB. J'ai vu sortie Log de multiprocessing.Processus - malheureusement, il ne répond pas à cette question.

je crée un processus enfant (sur windows) via multiprocessing. Je veux que tout de la sortie stdout et stderr du processus enfant soit redirigé vers un fichier journal, plutôt que d'apparaître à la console. La seule suggestion que j'ai vu, c'est pour l'enfant pour fixer sys.stdout à un fichier. Toutefois, cela ne ne pas rediriger efficacement toutes les sorties stdout, en raison du comportement de la redirection stdout sur Windows.

pour illustrer le problème, construire une DLL Windows avec le code suivant

#include <iostream>

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

ensuite créer et exécuter un script python comme le suivant, qui importe cette DLL et appelle la fonction:

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

pour voir le même comportement que moi, il est probablement nécessaire que la DLL soit construite contre un C différent l'exécution que celle que Python utilise. Dans mon cas, python est construit avec Visual Studio 2010, mais ma DLL est construite avec VS 2005.

le comportement que je vois est que la console montre:

> stdout_test.py

Writing to STDOUT from python, before redirect

Writing to STDOUT from test DLL

alors que le fichier stdout_redirect_log.txt finit par contenir:

Writing to STDOUT from python, after redirect

en d'autres termes, réglage sys.stdout n'a pas réussi à rediriger la sortie stdout générée par la DLL. Cela n'est pas surprenant étant donné la nature des IPA sous-jacents pour la redirection stdout dans Windows. J'ai déjà rencontré ce problème au niveau natif/C++ et je n'ai jamais trouvé de moyen fiable de rediriger stdout depuis un processus. Il doit être fait à l'extérieur.

c'est en fait la raison pour laquelle je lance un processus enfant - c'est pour que je puisse me connecter extérieurement à ses tuyaux et ainsi garantir que j'intercepte toute sa production. Je peux certainement le faire en lançant le processus manuellement avec pywin32, mais je serais très tout comme d'être en mesure d'utiliser les facilités de multiprocessing, en particulier la capacité de communiquer avec le processus enfant via un objet multiprocessing Pipe, afin d'obtenir des mises à jour de progrès. La question Est de savoir s'il existe un moyen d'utiliser à la fois le multiprocessing pour ses installations IPC et pour rediriger de manière fiable toutes les sorties stdout et stderr de l'enfant vers un fichier.

mise à jour: en regardant le code source pour multitraitement.Processus, il a un membre statique, _Popen, qui semble pouvoir être utilisé pour outrepasser la classe utilisée pour créer le processus. S'il est défini à None (par défaut), il utilise un multiprocessing.bifurquer._Popen, mais il semble qu'en disant

multiprocessing.Process._Popen = MyPopenClass

je pourrais annuler la création du processus. Cependant, bien que je pourrais dériver ceci de multiprocessing.bifurquer._Popen, on dirait que je devrais copier un tas de trucs internes dans mon implémentation, ce qui sonne feuilletée et pas très à l'avenir. Si c'est le seul choix, je pense que je serais probablement plump pour faire toute la chose manuellement avec pywin32 à la place.

25
demandé sur Community 2011-10-10 19:10:49

3 réponses

la solution que vous suggérez est bonne: créer vos processus manuellement de sorte que vous ayez un accès explicite à leurs poignées de fichiers stdout/stderr. Vous pouvez alors créer une socket pour communiquer avec le sous-processus et utiliser multiprocessing.connexion sur cette socket (multiprocessing.Pipe crée le même type d'objet de connexion, ce qui devrait vous donner à tous la même fonctionnalité IPC).

voici un exemple à deux fichiers.

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

vous pouvez également vouloir voir la première réponse à cette question pour obtenir non-blocage lit du sous-processus.

7
répondu Luke 2017-05-23 12:25:17

Je ne pense pas que vous ayez une meilleure option que de rediriger un sous-processus vers un fichier comme vous l'avez mentionné dans votre commentaire.

la façon dont les consoles stdin/out/err travail dans windows est chaque processus à sa naissance a son poignées std défini. Vous pouvez les changer avec SetStdHandle . Quand vous modifiez sys.stdout de python, vous ne modifiez que là où python imprime des trucs, pas là où d'autres DLL impriment des trucs. Une partie de la CRT dans votre DLL utilise GetStdHandle pour savoir où imprimer. Si vous voulez, vous pouvez faire n'importe quelle tuyauterie que vous voulez dans L'API windows dans votre DLL ou dans votre script python avec pywin32. Bien que je pense que ce sera plus simple avec subprocess .

1
répondu ubershmekel 2011-10-30 20:32:02

je suppose que je suis hors de la base et que je manque quelque chose, mais pour ce que ça vaut ici, c'est ce qui m'est venu à l'esprit quand j'ai lu votre question.

si vous pouvez intercepter tous les stdout et stderr (j'ai eu cette impression à partir de votre question), alors pourquoi ne pas ajouter ou envelopper cette fonctionnalité de capture autour de chacun de vos processus? Puis envoyer ce qui est capturé par une file d'attente à un consommateur qui peut faire ce que vous voulez avec toutes les sorties?

0
répondu KobeJohn 2012-01-23 15:35:35