Многопроцессорность Python: как НАДЕЖНО перенаправить stdout из дочернего процесса?

NB. Я видел Вывод журнала multiprocessing.Process - к сожалению, он не отвечает на этот вопрос.

Я создаю дочерний процесс (в Windows) через многопроцессорность. Я хочу, чтобы все выходные данные stdout и stderr дочернего процесса перенаправлялись в файл журнала, а не отображались на консоли. Единственное, что я видел, это то, что дочерний процесс должен установить sys.stdout в файл. Однако это не позволяет эффективно перенаправить весь вывод stdout из-за поведения перенаправления stdout в Windows.

Чтобы проиллюстрировать проблему, создайте Windows DLL с помощью следующего кода

#include 

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

Затем создайте и запустите сценарий python, подобный приведенному ниже, который импортирует эту DLL и вызывает функцию:

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

Чтобы увидеть то же поведение, что и Мне, вероятно, необходимо, чтобы DLL была построена для среды выполнения C, отличной от той, которую использует Python. В моем случае python построен с помощью 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 не смогла перенаправить вывод stdout, сгенерированный DLL. Это неудивительно, учитывая характер базовых API-интерфейсов для перенаправления stdout в Windows.Я раньше сталкивался с этой проблемой на уровне native / C ++ и никогда не находил способа надежно перенаправить stdout из процесса. Это нужно делать внешне.

На самом деле это и есть причина, по которой я запускаю дочерний процесс - это для того, чтобы я мог подключаться к его каналам извне и таким образом гарантировать, что я перехватываю весь его вывод. Я определенно могу сделать это, запустив процесс вручную с помощью pywin32, но мне бы очень хотелось иметь возможность использовать средства многопроцессорной обработки, в частности возможность общаться с дочерним процессом через многопроцессорный объект Pipe, чтобы добиться прогресса. обновления. Вопрос в том, есть ли способ одновременно использовать многопроцессорность для своих возможностей IPC и , чтобы надежно перенаправить весь вывод дочерних stdout и stderr в файл.

ОБНОВЛЕНИЕ: Если посмотреть на исходный код для multiprocessing.Processs, у него есть статический член _Popen, который, похоже, может использоваться для переопределения класса, используемого для создания процесса. Если он установлен в None (по умолчанию), он использует multiprocessing.forking._Popen, но похоже, что, сказав

multiprocessing.Process._Popen = MyPopenClass

, я мог бы переопределить создание процесса. Однако, хотя я мог бы получить это из multiprocessing.forking._Popen, похоже, что мне пришлось бы скопировать кучу внутренних вещей в свою реализацию, что звучит ненадежно и не очень перспективно. Если это единственный выбор, я бы, вероятно, решил сделать все это вручную с помощью pywin32.

27
задан Community 23 May 2017 в 12:25
поделиться

2 ответа

Хорошее решение, которое вы предлагаете: создайте свои процессы вручную, чтобы у вас был явный доступ к их дескрипторам файла 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

Вы также можете захотеть увидеть первый ответ на этот вопрос , чтобы получить неблокирующие чтения из подпроцесса.

9
ответ дан 28 November 2019 в 05:56
поделиться

Я предполагаю, что у меня нет базы и что-то упустил, но что стоит, вот что пришло мне в голову, когда я прочитал ваш вопрос.

Если вы можете перехватить все stdout и stderr (я получил это впечатление от вашего вопроса), то почему бы не добавить или не обернуть эту функцию захвата вокруг каждого из ваших процессов? Затем отправьте то, что записано через очередь, потребителю, который может делать все, что вы хотите со всеми выходами?

0
ответ дан 28 November 2019 в 05:56
поделиться
Другие вопросы по тегам:

Похожие вопросы: