在一个 C 程序中,我试图 fork 一个子进程来运行一个 Python 脚本,该脚本应该处理来自父进程 standard input 的行。并将结果返回给 standard output 上的父级,即父级将提供输入行并从子级标准输出中读取结果。
只有父级和子级都停留在互相读取第一行数据上。
为什么?
这是一个示例运行,从 C-parent 开始:
作为根用户:
./pycalc
输出:
dude
Got this to do calc with: dude
^CTraceback (most recent call last):
File "./pycalc.py", line 9, in <module>
line = raw_input()
KeyboardInterrupt
这是示例 python 脚本:
#!/usr/bin/python
import base64
import os
line = ''
while True:
try:
line = raw_input()
except EOFError:
break
print(base64.encodestring(line))
print('Bye - End of Calc!')
这是父 C 源代码:
#define PARENT_READ read_pipe[0]
#define PARENT_WRITE write_pipe[1]
#define CHILD_WRITE read_pipe[1]
#define CHILD_READ write_pipe[0]
int main(int argc, char *argv[])
{
int pid;
int read_pipe[2];
int write_pipe[2];
FILE *wrp = (FILE *)NULL;
FILE *rdp = (FILE *)NULL;
char line[1024+1];
if (pipe(read_pipe) < 0)
exit(-1);
if (pipe(write_pipe) < 0)
exit(-1);
if ((pid = fork()) < 0)
exit(-1);
else if (pid == 0) {
// Child process go here
close(PARENT_READ);
close(PARENT_WRITE);
dup2(CHILD_READ, STDIN_FILENO);
dup2(CHILD_WRITE, STDOUT_FILENO);
close(CHILD_READ);
close(CHILD_WRITE);
setlinebuf(stdin);
setlinebuf(stdout);
const char *argv[] = {"pycalc.py", NULL};
execvp("pycalc.py", (char **)argv);
exit(errno);
}
// Parent process go here
close(CHILD_READ);
close(CHILD_WRITE);
if ((wrp = fdopen(PARENT_WRITE, "w")) == NULL)
exit(-1);
setlinebuf(wrp);
if ((rdp = fdopen(PARENT_READ, "r")) == NULL)
exit(-1);
setlinebuf(rdp);
while (fgets(line, sizeof(line), stdin) != NULL) {
printf("Got this to do calc with: %s", line);
fprintf(wrp, "%s", line);
fflush(wrp);
fgets(line, sizeof(line), rdp);
printf("Got this calc from child: %s", line);
}
exit(0);
}
最佳答案
我已经使用自己设计的一些命令行工具进行了足够的实验,以说服我问题出在 Python 在管道上下文中的工作方式。它在收到 EOF 之前不会处理数据(因为输入的数据不够大,无法填充缓冲区)。因此,尽管 raw_input
尽了最大努力并尝试在执行 Python 之前设置行缓冲,但它不会在行可用时立即读取数据。我的测试管道连续有 6 个进程:
timecmd -m -- cat -u | tstamp -f3 | tee /dev/tty | timecmd -m -- pycalc.py |
tstamp -f3 | tee $(isodate -c).log
timecmd
命令报告命令的开始时间、运行命令并报告退出时间(-m
表示毫秒时间)。 tstamp -f3
在输出的每一行前面加上时间戳(-f3
表示毫秒时间)。使用 cat -u
进行无缓冲输出;它会报告线路可用; tee/dev/tty
在终端上显示带时间戳的输出并将其提供给 pycalc.py
;输出再次带有时间戳,然后通过管道传输到 tee
进行日志记录(与 isodate -c
组合生成一个日志文件名,例如 20180924.072626.log
)。当我使用它时,我清楚地看到,在我向 cat -u
指示 EOF 之前,pycalc.py
不会执行任何操作。
$ timecmd -m -- cat -u | tstamp -f3 | tee /dev/tty | timecmd -m -- pycalc.py |
> tstamp -f3 | tee $(isodate -c).log
2018-09-24 07:29:06.419 [PID 44960] cat -u
2018-09-24 07:29:06.419 [PID 44961] pycalc.py
dude
2018-09-24 07:29:09.274: dude
how
2018-09-24 07:29:10.819: how
are
2018-09-24 07:29:12.349: are
you
2018-09-24 07:29:16.540: you
2018-09-24 07:29:26.205 [PID 44960; status 0x0000] - 19.786s
2018-09-24 07:29:26.209 [PID 44961; status 0x0000] - 19.789s
2018-09-24 07:29:26.208: MjAxOC0wOS0yNCAwNzoyOTowOS4yNzQ6IGR1ZGU=
2018-09-24 07:29:26.209:
2018-09-24 07:29:26.209: MjAxOC0wOS0yNCAwNzoyOToxMC44MTk6IGhvdw==
2018-09-24 07:29:26.209:
2018-09-24 07:29:26.209: MjAxOC0wOS0yNCAwNzoyOToxMi4zNDk6IGFyZQ==
2018-09-24 07:29:26.209:
2018-09-24 07:29:26.209: MjAxOC0wOS0yNCAwNzoyOToxNi41NDA6IHlvdQ==
2018-09-24 07:29:26.209:
2018-09-24 07:29:26.209: Bye - End of Calc!
因此,AFAICT,您需要找到一种方法,使 Python 在标准输入来自管道时从标准输入读取行,并在缓冲区已满之前读取它们。在那之前,你不会成功。在确保正确(及时)刷新输出方面,您可能也会遇到类似的问题。您可以通过对 Python 看到的输入添加时间戳来进一步完善我所做的测试,也许可以将带时间戳的数据报告给标准错误(这样您就知道 Python 在做什么),但我相当确信我的设置及时将数据发送到 Python,但 Python没有意识到这一点。
用 Google 搜索“python 行缓冲标准输入”,结果是 Setting smaller buffer size for sys.stdin这建议使用python -u
。当我在长命令行中使用 timecmd -m -- python -u pycalc.py 时,我会得到预期的输出 - Python 在生成数据时做出响应。
$ timecmd -m -- cat -u | tstamp -f3 | tee /dev/tty | timecmd -m -- python -u pycalc.py |
> tstamp -f3 | tee $(isodate -c).log
2018-09-24 07:52:41.485 [PID 45180] cat -u
2018-09-24 07:52:41.485 [PID 45181] python -u pycalc.py
dude
2018-09-24 07:52:43.213: dude
2018-09-24 07:52:43.214: MjAxOC0wOS0yNCAwNzo1Mjo0My4yMTM6IGR1ZGU=
2018-09-24 07:52:43.215:
how
2018-09-24 07:52:48.852: how
2018-09-24 07:52:48.852: MjAxOC0wOS0yNCAwNzo1Mjo0OC44NTI6IGhvdw==
2018-09-24 07:52:48.852:
are
2018-09-24 07:52:50.720: are
2018-09-24 07:52:50.720: MjAxOC0wOS0yNCAwNzo1Mjo1MC43MjA6IGFyZQ==
2018-09-24 07:52:50.720:
you
2018-09-24 07:52:52.479: you
2018-09-24 07:52:52.479: MjAxOC0wOS0yNCAwNzo1Mjo1Mi40Nzk6IHlvdQ==
2018-09-24 07:52:52.479:
2018-09-24 07:52:53.646 [PID 45180; status 0x0000] - 12.161s
2018-09-24 07:52:53.647: Bye - End of Calc!
2018-09-24 07:52:53.650 [PID 45181; status 0x0000] - 12.165s
您可以从时间戳中看到打字(故意慢速打字)发生时的 react 。将其转化为您的程序需要执行的操作,我得出:
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define PARENT_READ read_pipe[0]
#define PARENT_WRITE write_pipe[1]
#define CHILD_WRITE read_pipe[1]
#define CHILD_READ write_pipe[0]
int main(void)
{
int pid;
int read_pipe[2];
int write_pipe[2];
if (pipe(read_pipe) < 0)
exit(-1);
if (pipe(write_pipe) < 0)
exit(-1);
if ((pid = fork()) < 0)
exit(-1);
else if (pid == 0)
{
// Child process go here
close(PARENT_READ);
close(PARENT_WRITE);
dup2(CHILD_READ, STDIN_FILENO);
dup2(CHILD_WRITE, STDOUT_FILENO);
close(CHILD_READ);
close(CHILD_WRITE);
char *argv[] = {"python", "-u", "pycalc.py", NULL};
execvp(argv[0], argv);
fprintf(stderr, "failed to execute %s (%d: %s)\n", argv[0], errno, strerror(errno));
exit(errno);
}
// Parent process go here
close(CHILD_READ);
close(CHILD_WRITE);
FILE *wrp = fdopen(PARENT_WRITE, "w");
FILE *rdp = fdopen(PARENT_READ, "r");
if (wrp == NULL || rdp == NULL)
exit(-1);
setlinebuf(wrp);
setlinebuf(rdp);
char line[1024 + 1];
while (fgets(line, sizeof(line), stdin) != NULL)
{
printf("Got this to do calc with: %s", line);
fprintf(wrp, "%s", line);
fflush(wrp);
fgets(line, sizeof(line), rdp);
printf("Got this calc from child: %s", line);
}
return(0);
}
运行时(程序是pipe43
),我得到:
$ pipe43
dude
Got this to do calc with: dude
Got this calc from child: ZHVkZQ==
how
Got this to do calc with: how
Got this calc from child:
are
Got this to do calc with: are
Got this calc from child: aG93
you
Got this to do calc with: you
Got this calc from child:
Traceback (most recent call last):
File "pycalc.py", line 15, in <module>
print('Bye - End of Calc!')
IOError: [Errno 32] Broken pipe
Python 的错误是因为它尝试在父进程关闭其管道后进行写入 — 它不会等待来自 Python 的杂散输出。
我不确定 -u
选项对 Python 的效率影响;我怀疑他们不太好。
关于python - 在父进程和子进程之间使用管道,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52477795/