往返于 2 Python 个子流程的循环管道

Circle Piping to and from 2 Python Subprocesses

我需要有关子流程模块的帮助。这个问题听起来可能会重复,而且我已经看到许多以多种方式与它相关的文章。但即使这样我也无法解决我的问题。它是这样的:
我有一个C程序2.c它的内容如下:

#include<stdio.h>
int main()
{
int a;
scanf("%d",&a);
while(1)
  {
   if(a==0)               //Specific case for the first input
     {
      printf("%d\n",(a+1));
      break;
     }
   scanf("%d",&a);
   printf("%d\n",a);
  }
return 0;
}

我需要编写一个 python 脚本,它首先使用 subprocess.call() 编译代码,然后使用 Popen 打开两个进程以执行相应的 C 程序。现在第一个过程的输出必须是第二个过程的输入,反之亦然。所以本质上,如果我的初始输入是 0,那么第一个进程输出 2,它被第二个进程采用。它依次输出 3 等等。

下面的脚本是我的想法,但它有缺陷。如果有人能帮助我,我将不胜感激。

from subprocess import *
call(["gcc","2.c"])
a = Popen(["./a.out"],stdin=PIPE,stdout=PIPE) #Initiating Process
a.stdin.write('0')
temp = a.communicate()[0]
print temp
b = Popen(["./a.out"],stdin=PIPE,stdout=PIPE) #The 2 processes in question
c = Popen(["./a.out"],stdin=PIPE,stdout=PIPE)
while True:
    b.stdin.write(str(temp))
    temp = b.communicate()[0]
    print temp
    c.stdin.write(str(temp))
    temp = c.communicate()[0]
    print temp
a.wait()
b.wait()
c.wait()

问题出在这里

while True:
    b.stdin.write(str(temp))
    temp = b.communicate()[0]
    print temp
    c.stdin.write(str(temp))
    temp = c.communicate()[0]
    print temp

一旦 communicate 返回,它不会注意到更多。您必须再次 运行 该过程。另外,您不需要同时打开 2 个进程。

此外,初始化阶段与 运行ning 阶段没有区别,除了您提供输入数据。

您可以做些什么来简化并使其发挥作用:

from subprocess import *
call(["gcc","2.c"])
temp = str(0)

while True:
    b = Popen(["./a.out"],stdin=PIPE,stdout=PIPE) #The 2 processes in question
    b.stdin.write(temp)
    temp = b.communicate()[0]
    print temp
    b.wait()

否则,要并行查看 2 个进程 运行ning,证明您可以做到这一点,只需按如下方式修复循环(通过在循环中移动 Popen 调用):

while True:

    b = Popen(["./a.out"],stdin=PIPE,stdout=PIPE) #The 2 processes in question
    c = Popen(["./a.out"],stdin=PIPE,stdout=PIPE)

    b.stdin.write(str(temp))
    temp = b.communicate()[0]
    print temp
    c.stdin.write(str(temp))
    temp = c.communicate()[0]
    print temp

更好。 b 输出馈送 c 输入:

while True:

    b = Popen(["./a.out"],stdin=PIPE,stdout=PIPE) #The 2 processes in question
    c = Popen(["./a.out"],stdin=b.stdout,stdout=PIPE)

    b.stdin.write(str(temp))
    temp = c.communicate()[0]
    print temp

如果您希望第一个命令 a 的输出作为第二个命令 b 的输入,那么 b 的输出又是 a的输入——像一条蛇吃它的尾巴一样绕圈——然后你不能在循环中使用 .communicate().communicate() 不会 return 直到进程结束并且所有输出被消耗。

一种解决方案是使用命名管道(如果 open() 在您的系统上不会在这种情况下阻塞):

#!/usr/bin/env python3
import os
from subprocess import Popen, PIPE

path = 'fifo'
os.mkfifo(path) # create named pipe
try:
    with open(path, 'r+b', 0) as pipe, \
         Popen(['./a.out'], stdin=PIPE, stdout=pipe) as b, \
         Popen(['./a.out'], stdout=b.stdin, stdin=pipe) as a:
        pipe.write(b'10\n') # kick-start it
finally:
    os.remove(path) # clean up

它模拟来自 @alexander barakin answera < fifo | b > fifo shell 命令。


这里是更复杂 的解决方案,它通过 python parent 过程收集数据:

#!/usr/bin/env python3
import shutil
from subprocess import Popen, PIPE

with Popen(['./a.out'], stdin=PIPE, stdout=PIPE, bufsize=0) as b, \
     Popen(['./a.out'], stdout=b.stdin, stdin=PIPE, bufsize=0) as a:
    a.stdin.write(b'10\n') # kick-start it
    shutil.copyfileobj(b.stdout, a.stdin) # copy b's stdout to a' stdin

此代码通过 OS 管道使用重定向将 a 的输出连接到 b 的输入(就像 a | b shell 命令所做的那样)。

为了完成循环,b 的输出被复制到 a 在 parent Python 代码中使用 shutil.copyfileobj() 的输入。

此代码可能有 buffering issues:进程之间有多个缓冲区:C stdio 缓冲区,Python 文件中的缓冲区 objects 包装管道(由 [=28 控制) =]).

bufsize=0 关闭 Python 端的缓冲,数据可用时立即复制。当心,bufsize=0 可能会导致部分写入——您可能需要内联 copyfileobj() 并再次调用 write() 直到写入所有读取数据。

调用 setvbuf(stdout, (char *) NULL, _IOLBF, 0),在您的 C 程序中生成标准输出 line-buffered:

#include <stdio.h>

int main(void)
{
  int a;
  setvbuf(stdout, (char *) NULL, _IOLBF, 0); /* make line buffered stdout */
  do {
    scanf("%d",&a);
    printf("%d\n",a-1);
    fprintf(stderr, "%d\n",a); /* for debugging */
  } while(a > 0);
  return 0;
}

输出

10
9
8
7
6
5
4
3
2
1
0
-1

输出相同。

由于 C child 程序的编写和执行方式,您可能还需要在 a.stdin.write() and/or 末尾捕获并忽略 BrokenPipeError 异常a.stdin.close()a 进程可能已经停止,而 b 有未复制的数据)。