Python中主进程与子进程的动态通信

2024-03-29 11:15:18 发布

您现在位置:Python中文网/ 问答频道 /正文

我在Python中工作,我想找到一个工作流来支持两个进程(main processsub-process)相互通信。我指的是主进程子进程发送一些数据的能力(也许,通过写入子进程的stdin),以及子进程向主进程发送一些数据的能力。这也意味着两者都可以读取发送给他们的数据(我正在考虑从stdin读取)。在

我试图使用子进程库,但它似乎旨在处理那些只提供一次输出然后终止的进程,而我希望动态交换数据并仅在收到这样的命令时关闭子进程。在

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

我对使用第三方库持开放态度,但如果您提出一个完全基于python标准库的解决方案,效果会更好。在


Tags: 数据答案命令进程mainstdin动态能力
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标志)。否则同上。在

子代码:

^{pr2}$

父脚本:

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的,这些只是关于如何使用管道作为一个选项的粗略提示。在

您希望使用subprocess.PIPE创建一个Popen对象作为标准输入和输出,并使用其file对象进行通信,而不是使用一个run(以及更老、更具体的像check_output)的控制柄进行通信。挑战在于避免死锁:在这样一种情况下很容易着陆:每个进程都在试图写,管道缓冲区被填满(因为没有人从它们中读取数据),所有的东西都挂起。您还必须记住在这两个进程中flush,以避免请求或响应卡在file对象的缓冲区中。在

提供Popen.communicate是为了避免这些问题,但它只支持单个字符串(而不是正在进行的会话)。传统的解决方案是select,但它也可以使用单独的线程来发送请求和读取结果。(这是使用CPython线程而不使用GIL的原因之一:每个线程都是为了运行而另一个线程被阻塞,所以很少有争用。)当然,同步是一个问题,您可能需要做一些工作,使多线程客户机在外部像一个简单的同步函数调用一样工作。在

注意两个进程都需要flush,但是如果其中一个实现这样的非阻塞I/O就足够了;一个进程通常在启动另一个进程的进程中执行该任务,因为这是已知的必要的(而这类程序是例外)。在

相关问题 更多 >