Python中主要和子进程之间的动态通信

Ant*_*ent 11 python subprocess python-3.x

我在Python工作,我想找到一个工作流程,使两个流程(主流程子流程)能够相互通信.通过这种方式,我的意思是主进程将一些数据发送到子进程(可能是通过写入子进程的 stdin)的能力以及子进程将一些数据发送回主进程的能力.这也意味着两者都可以读取发送给他们的数据(我正在考虑从stdin读取).

我试图使用进程库,但它似乎打算使用旨在只提供一次输出然后终止的进程,而我想动态交换数据并仅在这样的命令时关闭子进程收到了.

我在StackOverflow上阅读了很多关于与我的问题密切相关的问题的答案,但是我没有找到令人满意的答案,因为这些答案的意思与我的不同之处在于一个重要的细节:我需要我的主要流程来能够根据需要动态地与其子进程交换数据,而不仅仅是一次,这反过来意味着子进程应该运行,直到它从主进程接收到终止的某个命令.

我愿意使用第三方库,但如果你提出一个完全基于Python标准库的解决方案会更好.

Ond*_* K. 7

看起来管道可能是适合您的用例的选择。但要注意的是,正常情况下,读端和写端都希望分别写入或读取数据。还要确保您不会对缓冲感到惊讶(没有任何结果,因为除非在预期边界上,否则缓冲区不会自动刷新,除非进行相应设置)。

如何在两个进程之间使用两个管道(它们是单向的)的基本示例:

import os

def child():
    """This function is executed in a child process."""
    infile = os.fdopen(r1)
    outfile = os.fdopen(w2, 'w', buffering=1)
    for line in infile:
        if line.rstrip() == 'quit':
            break
        print(line.upper(), end='', file=outfile)

def parent():
    """This function is executed in a parent process."""
    outfile = os.fdopen(w1, 'w', buffering=1)
    infile = os.fdopen(r2)
    print('Foo', file=outfile)
    print(infile.readline(), end='')
    print('bar', file=outfile)
    print(infile.readline(), end='')
    print('quit', file=outfile)

(r1, w1) = os.pipe()  # for parent -> child writes
(r2, w2) = os.pipe()  # for child -> parent writes
pid = os.fork()
if pid == 0:
    child()  # child code runs here
elif pid > 0:
    parent()  # parent code runs here
    os.waitpid(pid, 0)  # wait for child
else:
    raise RuntimeError("This should not have happened.")
Run Code Online (Sandbox Code Playgroud)

事实上,使用它会更容易、更实用subprocess,而且您可能想运行另一个程序。前者需要被告知不要关闭管道文件描述符,后者需要管道文件描述符是可继承的(没有O_CLOEXEC设置标志)。

儿童计划:

import os
import sys

infile = os.fdopen(int(sys.argv[1]))
outfile = os.fdopen(int(sys.argv[2]), 'w', buffering=1)    
for line in infile:
    if line.rstrip() == 'quit':
        break
    print(line.upper(), end='', file=outfile)
Run Code Online (Sandbox Code Playgroud)

家长计划:

import os
import subprocess

(r1, w1) = os.pipe2(0)  # for parent -> child writes
(r2, w2) = os.pipe2(0)  # for child -> parent writes    
child = subprocess.Popen(['./child.py', str(r1), str(w2)], pass_fds=(r1, w2))
outfile = os.fdopen(w1, 'w', buffering=1)
infile = os.fdopen(r2)
print('Foo', file=outfile)
print(infile.readline(), end='')
print('bar', file=outfile)
print(infile.readline(), end='')
print('quit', file=outfile)
child.wait()
Run Code Online (Sandbox Code Playgroud)

如果子程序不需要标准输入或标准输出,则可以使用它们分别从子程序中获取信息。这甚至会更简单。

儿童计划:

import sys

for line in sys.stdin:
    if line.rstrip() == 'quit':
        break
    print(line.upper(), end='', flush=True)
Run Code Online (Sandbox Code Playgroud)

家长计划:

import os
import subprocess

(r1, w1) = os.pipe2(0)  # for parent -> child writes
(r2, w2) = os.pipe2(0)  # for child -> parent writes
child = subprocess.Popen(['./child.py'], stdin=r1, stdout=w2)
outfile = os.fdopen(w1, 'w', buffering=1)
infile = os.fdopen(r2)
print('Foo', file=outfile)
print(infile.readline(), end='')
print('bar', file=outfile)
print(infile.readline(), end='')
print('quit', file=outfile)
child.wait()
Run Code Online (Sandbox Code Playgroud)

如前所述,它并不是真正特定于 Python 的,这些只是关于如何使用管道作为一种选项的粗略提示。

  • 该示例使用更高级别(python)接口通过[io.TextIOWrapper](https://docs.python.org/3/library/io.html#io.TextIOWrapper)读取/写入(在中打开类似文件的对象)文本模式)。*“如果 line_buffering 为 True,则当对 write 的调用包含换行符或回车符时,隐含了flush()。”*我并不完全不同意你的观点,但我也试图降低复杂性。 (2认同)

Dav*_*ing 5

您希望为标准输入和输出创建一个Popen对象,subprocess.PIPE并使用其文件对象进行通信——而不是使用像 那样的cantrips之一run(以及更老的、更具体的像check_output)。挑战在于避免死锁:很容易陷入这样一种情况:每个进程都在尝试写入,管道缓冲区已满(因为没有人正在读取它们),并且一切都挂起。您还必须记住flush在这两个进程中,以避免请求或响应卡在file对象的缓冲区中。

Popen.communicate提供以避免这些问题,但它仅支持单个字符串(而不是正在进行的对话)。传统的解决方案是select,但它也适用于使用单独的线程来发送请求和读取结果。(这是尽管有 GIL 仍然使用 CPython 线程的原因之一:每个线程都可以运行,而另一个线程被阻塞,因此很少有争用。)当然,同步是一个问题,您可能需要做一些努力使多线程客户端表现得像一个简单的、同步的外部函数调用。

请注意,两个进程都需要flush,但是如果其中一个实现了这种非阻塞 I/O就足够了;一个通常在启动另一个的过程中完成这项工作,因为这是已知必要的(并且此类程序是例外)。