将 sys.stdout 作为参数传递给进程
Passing sys.stdout as an argument to a process
我将 "sys.stdout" 作为参数传递给一个进程,然后该进程在执行它的操作时写入 "sys.stdout"。
import multiprocessing
import sys
def worker_with(stream):
stream.write('In the process\n')
if __name__ == '__main__':
sys.stdout.write('In the main\n')
lock = multiprocessing.Lock()
w = multiprocessing.Process(target=worker_with, args=(sys.stdout,))
w.start()
w.join()
上面的代码不起作用,它 returns 出现以下错误:"ValueError: operation on closed file".
我尝试了 运行 相同的代码,但直接调用函数而不是生成进程并且它有效,它打印到控制台。
我还尝试了 运行 相同的代码,但在函数内部直接调用 sys.stdout,将其生成为一个进程并且它可以工作。
问题似乎是将 sys.stout 作为进程的参数传递。
有人知道为什么吗?
注意:此代码的灵感来自教程 PYMOTW - 进程间通信。
编辑:我是 运行 Python 2.7.10,Windows7.
上的 32 位
当您将参数传递给 Process
时,它们在父级中被 pickle,传输给子级,并在那里被 unpickled。不幸的是,看起来通过 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 是辅助任务,I/O 通过主进程执行(因为如果它们都独立写入同一个文件句柄,交错写入就会出现问题)。
至少在 Python 3.5 中,他们修复了这个问题,因此错误是直接和明显的(由 open
、TextIOWrapper
编辑的类文件对象 return 和Buffered*
,用任何协议 pickle 时都会出错。
你在 Windows 上能做的最好的事情就是将已知的文件描述符作为参数发送:
sys.stdout.flush() # Precaution to minimize output interleaving
w = multiprocessing.Process(target=worker_with, args=(sys.stdout.fileno(),))
然后使用 os.fdopen
在另一侧重新打开它。对于 fd
不是标准句柄的一部分(0
、1
和 2
),因为 Windows 使用 "spawn" 方法制作新句柄Process
是的,您需要确保在 __name__ != "__main__"
时 import
ing __main__
模块的结果打开了任何此类 fd
(Windows 通过导入 __main__
模块模拟 fork
,将 __name__
设置为其他内容)。当然,如果它是一个命名文件,而不是标准句柄,您可以只传递名称并重新打开它。例如,要实现此功能,您需要更改:
def worker_with(stream):
stream.write('In the process\n')
至:
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.fdopen
之前使用 os.dup
复制句柄,因此这两个句柄彼此独立。
其他解决方案包括通过 multiprocessing.Pipe
将结果写回主进程(因此主进程负责将数据传递给 sys.stdout
,可能会启动一个线程来执行此工作异步),或使用更高级别的构造(例如 multiprocessing.Pool().*map*
),return 数据使用 return
语句而不是显式文件 I/O.
如果您真的迫切希望使所有文件描述符都通用(并且不关心可移植性),而不仅仅是在 import
上创建的标准句柄和描述符 __main__
,您可以使用 the undocumented Windows utility function multiprocessing.forking.duplicate
用于将文件描述符从一个进程显式复制到另一个进程;这将是令人难以置信的 hacky(你需要查看 multiprocessing.forking.Popen
的 Windows 定义的其余部分以了解如何使用它),但它至少允许传递任意文件描述符,不仅仅是静态打开的。
我将 "sys.stdout" 作为参数传递给一个进程,然后该进程在执行它的操作时写入 "sys.stdout"。
import multiprocessing
import sys
def worker_with(stream):
stream.write('In the process\n')
if __name__ == '__main__':
sys.stdout.write('In the main\n')
lock = multiprocessing.Lock()
w = multiprocessing.Process(target=worker_with, args=(sys.stdout,))
w.start()
w.join()
上面的代码不起作用,它 returns 出现以下错误:"ValueError: operation on closed file".
我尝试了 运行 相同的代码,但直接调用函数而不是生成进程并且它有效,它打印到控制台。 我还尝试了 运行 相同的代码,但在函数内部直接调用 sys.stdout,将其生成为一个进程并且它可以工作。 问题似乎是将 sys.stout 作为进程的参数传递。
有人知道为什么吗?
注意:此代码的灵感来自教程 PYMOTW - 进程间通信。
编辑:我是 运行 Python 2.7.10,Windows7.
上的 32 位当您将参数传递给 Process
时,它们在父级中被 pickle,传输给子级,并在那里被 unpickled。不幸的是,看起来通过 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 是辅助任务,I/O 通过主进程执行(因为如果它们都独立写入同一个文件句柄,交错写入就会出现问题)。
至少在 Python 3.5 中,他们修复了这个问题,因此错误是直接和明显的(由 open
、TextIOWrapper
编辑的类文件对象 return 和Buffered*
,用任何协议 pickle 时都会出错。
你在 Windows 上能做的最好的事情就是将已知的文件描述符作为参数发送:
sys.stdout.flush() # Precaution to minimize output interleaving
w = multiprocessing.Process(target=worker_with, args=(sys.stdout.fileno(),))
然后使用 os.fdopen
在另一侧重新打开它。对于 fd
不是标准句柄的一部分(0
、1
和 2
),因为 Windows 使用 "spawn" 方法制作新句柄Process
是的,您需要确保在 __name__ != "__main__"
时 import
ing __main__
模块的结果打开了任何此类 fd
(Windows 通过导入 __main__
模块模拟 fork
,将 __name__
设置为其他内容)。当然,如果它是一个命名文件,而不是标准句柄,您可以只传递名称并重新打开它。例如,要实现此功能,您需要更改:
def worker_with(stream):
stream.write('In the process\n')
至:
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.fdopen
之前使用 os.dup
复制句柄,因此这两个句柄彼此独立。
其他解决方案包括通过 multiprocessing.Pipe
将结果写回主进程(因此主进程负责将数据传递给 sys.stdout
,可能会启动一个线程来执行此工作异步),或使用更高级别的构造(例如 multiprocessing.Pool().*map*
),return 数据使用 return
语句而不是显式文件 I/O.
如果您真的迫切希望使所有文件描述符都通用(并且不关心可移植性),而不仅仅是在 import
上创建的标准句柄和描述符 __main__
,您可以使用 the undocumented Windows utility function multiprocessing.forking.duplicate
用于将文件描述符从一个进程显式复制到另一个进程;这将是令人难以置信的 hacky(你需要查看 multiprocessing.forking.Popen
的 Windows 定义的其余部分以了解如何使用它),但它至少允许传递任意文件描述符,不仅仅是静态打开的。