当我尝试在python中使用多个子进程进行异步渲染时遇到了同样的问题,这些子进程需要以低延迟与主进程通信。
当我将subprocess.popen() 与stdin=subprocess.PIPE 一起使用时,我发现子进程在stdin.close() 发生或主进程退出之前无法获取任何内容,两者都发送EOF 信号但使PIPE 成为一次性的。当然我试过stdin.writelines()、stdin.flush()、pickle.dump()等,但都没有成功。
但是有一种方法可以通过 NumPy 与子进程重复通信。
ndarray.tofile 可以直接将数组发送到文件对象。虽然文档声明它等同于 file.write(a.tobytes()),但它确实有意义。我很困惑,直到我在文档页面的末尾读到这个:
当fid为文件对象时,数组内容直接写入文件,绕过文件对象的write方法。因此,tofile 不能与支持压缩的文件对象(例如 GzipFile)或不支持 fileno() 的类文件对象(例如 BytesIO)一起使用。
其实我觉得是file.write()的错。任何调用write() 方法的函数都不可避免地无法发送EOF,除非我们绕过write() 方法,而这在不使用NumPy 等C 扩展的情况下是不可能的。
现在通过 PIPE 发送一般数据有两种方式:
-
NumPy 支持dtype=object,这意味着您可以直接将消息打包到对象数组中。另见numpy.lib.format。
存储对象数组,即包含任意 Python 对象元素的数组。具有对象数组的文件不可映射,但可以读取和写入磁盘。
-
如果 Struct 具有常规模式,您可以将其声明为 dtype 来打包您的消息,这就是我的情况。这是我的例子。
task = np.dtype([( "index", np.uint8 ),
( "text", np.unicode_, 128 ),
( "color", np.uint8, 2 ),
( "size", np.uint8 )])
for i in range(123):
np.empty(1, dtype=task).tofile(s.stdin) # s is the subprocess' name.
time.sleep(1)
然后我在子进程中分别成功获取了123次消息。
我真的希望这可以帮助你。因为我花了将近 4 天的时间才找到这个解决方案。我几乎正在考虑使用磁盘上的真实文件来完成进程之间的通信——这应该会更慢——但是感谢 NumPy,我的调试终于结束了......
另外,我认为np.save() 发送 EOF 毫无意义。您可以在 python 控制台中尝试此操作。
>>> import numpy as np
>>> import sys
>>> a = np.arange(100).reshape(10,10)
>>> a.tofile(sys.stdout.buffer)
... some garbled characters ...
>>> a.tofiler(sys.stdout)
... some garbled characters ...
>>> np.save(sys.stdout.buffer, a)
... some garbled characters ...
>>> np.save(sys.stdout, a)
Traceback (most recent call last):
File "<input>", line 1, in <module>
File "<__array_function__ internals>", line 5, in save
...
TypeError: write() argument must be str, not bytes
原因是sys.stdout.buffer.write()接受bytes参数,而sys.stdout.write()接受str。因此,使用 array.tofile 写入 sys.stdout 不会导致任何错误,表明它没有调用 write() 方法,而 np.save() 调用了。这引发了一个问题,似乎np.fromfile 不支持dtype=object 模式。对此感到抱歉。也许通过管道通过进程传输动态类型的数据真的很难,但我听说在ctype模块内部有一些方法可以在进程之间共享RAM,这可能会有所帮助。
提到我未能在终端(io.UnsupportedOperation: seek)中运行上面的脚本,但它在PyCharm的python控制台中运行良好。我对此一无所知。也许 PyCharm 的 python 控制台实际上也有 sys.stdin 的代理。
另外,subprocess.PIPE 似乎有一个最大缓冲区大小,因此传输渲染图像是不可能的。作为我的实验结果,将它们分成块并没有帮助。