Когда вы передаете аргументы Process
, они сохраняются в родительском объекте, передаются дочернему объекту и там распаковываются. К сожалению, похоже, что круговое путешествие через pickle
молча ведет себя неправильно для файловых объектов; с протоколом 0 он выдает ошибку, но с протоколом 2 (самый высокий протокол Python 2 и тот, который используется для multiprocessing
) он молча создает объект нежелательного файла:
>>> import pickle, sys
>>> pickle.loads(pickle.dumps(sys.stdout, pickle.HIGHEST_PROTOCOL))
<closed file '<uninitialized file>', mode '<uninitialized file>' at 0xDEADBEEF>
Та же проблема возникает и для именованных файлов; это не уникально для стандартных ручек. По сути, pickle
не может передавать файловый объект туда и обратно; даже когда он заявляет об успехе, результат - мусор.
Как правило, от multiprocessing
не ожидается, что он справится с таким сценарием; обычно Process
es являются рабочими задачами, а ввод-вывод выполняется через основной процесс (потому что, если бы все они записывались независимо в один и тот же дескриптор файла, у вас были бы проблемы с чередующейся записью).
По крайней мере, в Python 3.5 они исправили это, поэтому ошибка стала немедленной и очевидной (файлоподобные объекты, возвращаемые open
, TextIOWrapper
и Buffered*
, будут выдавать ошибку при обработке любым протоколом).
Лучшее, что вы можете сделать в Windows, это отправить известный файловый дескриптор в качестве аргумента:
sys.stdout.flush() # Precaution to minimize output interleaving
w = multiprocessing.Process(target=worker_with, args=(sys.stdout.fileno(),))
затем снова откройте его с другой стороны, используя os.fdopen
. Для fd
s, не являющихся частью стандартных дескрипторов (0
, 1
и 2
), поскольку Windows использует метод «порождения» для создания новых Process
es, вам необходимо убедиться, что все такие fd
были открыты как следствие import
ing модуля __main__
, когда __name__ != "__main__"
(Windows имитирует fork
, импортируя модуль __main__
, устанавливая __name__
на что-то другое). Конечно, если это именованный файл, а не стандартный дескриптор, вы можете просто передать имя и снова открыть его. Например, чтобы это работало, вы должны изменить:
def worker_with(stream):
stream.write('In the process\n')
to:
import os
def worker_with(toopen):
opener = open if isinstance(toopen, basestring) else os.fdopen
with opener(toopen, 'a') as stream:
stream.write('In the process\n')
Примечание. Как написано, если fd
соответствует одному из стандартных дескрипторов, os.fdopen
закроет базовый файловый дескриптор при выходе из инструкции with
, что может быть не совсем тем, что вам нужно. Если вам нужны файловые дескрипторы, чтобы пережить закрытие блока with
, при передаче файлового дескриптора вы можете использовать os.dup
для дублирования дескриптора перед вызовом os.fdopen
, чтобы два дескриптора не зависели друг от друга.
Другие решения могут включать в себя запись результатов обратно в основной процесс через multiprocessing.Pipe
(поэтому основной процесс отвечает за передачу данных в sys.stdout
, возможно, запуск потока для асинхронного выполнения этой работы) или использование конструкций более высокого уровня (например, multiprocessing.Pool().*map*
), которые возвращать данные с помощью оператора return
вместо явного файлового ввода-вывода.
Если вы действительно отчаянно хотите, чтобы это работало вообще для всех дескрипторов файлов (и вас не волнует переносимость), а не только для стандартных дескрипторов и дескрипторов, созданных на import
из __main__
, вы можете использовать недокументированная служебная функция Windows multiprocessing.forking.duplicate
, которая используется для явного дублирования файлового дескриптора. от одного процесса к другому; это было бы невероятно хакерским (вам нужно было бы посмотреть остальную часть определения multiprocessing.forking.Popen
в Windows, чтобы увидеть, как оно будет использоваться), но, по крайней мере, оно позволило бы передавать произвольные файловые дескрипторы, а не только статически открытые.
person
ShadowRanger
schedule
16.12.2015