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

时间:2019-01-06 09:35:36

标签: python python-3.x subprocess

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

我试图使用 subprocess 库,但是似乎它旨在与旨在仅输出一次然后终止的进程一起使用,而我想交换数据动态并仅在收到这样的命令时关闭子进程。

我在此处阅读了许多关于StackOverflow解决与我的问题密切相关的问题的答案,但是我都没有一个令人满意的答案,因为这些答案在一个重要的细节上与我的问题有所不同:我需要我的< em> main-process ,使其能够根据需要动态地交换其子数据 许多次数据,而不仅仅是一次,这意味着子进程应该运行,直到它从 main-process 收到某个命令终止。

我愿意使用第三方库,但是如果您提出仅基于Python标准库的解决方案,那就更好了。

2 个答案:

答案 0 :(得分:3)

您希望使用Popen创建一个subprocess.PIPE对象以进行标准输入和输出,并使用其文件对象进行通信-而不是使用{{ 1}}(以及更旧,更具体的版本,例如run)。面临的挑战是如何避免死锁:在每个进程都试图编写,管道缓冲区已满(因为没有人正在从中读取)并且所有进程都挂起的情况下,很容易着手。您还必须记住在两个过程中都 check_output ,以避免请求或响应卡在flush对象的缓冲区中。

提供

file是为了避免这些问题,但它仅支持单个字符串(而不是正在进行的对话)。传统的解决方案是Popen.communicate,但是它也可以使用单独的线程来发送请求和读取结果。 (这是尽管使用GIL仍使用CPython线程的原因之一:每个线程都存在,而另一个线程被阻塞,因此运行时几乎没有争用。)当然,同步是一个问题,并且您可能需要做一些工作来使多线程客户端像外部的简单同步函数调用一样。

请注意,两个进程都需要select,但是只要两个实现此类非阻塞I / O就足够了;一个人通常会在开始另一个人的过程中完成这项工作,因为众所周知这是必要的(并且此类程序除外)。

答案 1 :(得分:2)

似乎管道可能是您的用例的合适选择。请注意,在正常情况下,读写双方都希望数据被写入或消耗。还要确保不要对缓冲感到惊讶(什么都不会做,因为除非在相应的边界上设置了相应的缓冲区,否则缓冲区不会自动刷新)。

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

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
        outfile.write(line.upper())

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.")
# Once returned from corresponding function, both processes exit

实际上,使用subprocess更加容易和实用,并且您可能想exec使用另一个二进制文件/文件。需要告知前者不要关闭(至少是相关的管道)文件描述符,后者需要管道文件描述符是可继承的(未设置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
    outfile.write(line.upper())

父脚本:

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

想到这一点,我忘了问,孩子是不是需要stdin / out来做任何事情,还是可以用它来获取/获取信息。那会更简单:

孩子:

import sys

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

父母:

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

如前所述,它实际上并不是特定于python的,而只是关于如何使用管道作为一种选择的粗略提示。