subprocess.Popen.stdout - 实时读取 stdout (再次)

13 投票
5 回答
14534 浏览
提问于 2025-04-16 00:34

又是同样的问题。
原因是 - 在阅读了以下内容后,我仍然无法让它工作:

我的情况是,我有一个用C写的控制台应用程序,举个例子,这段代码在一个循环中:

tmp = 0.0;   
printf("\ninput>>"); 
scanf_s("%f",&tmp); 
printf ("\ninput was: %f",tmp); 

它不断读取一些输入并写出一些输出。

我用来与它交互的Python代码如下:

p=subprocess.Popen([path],stdout=subprocess.PIPE,stdin=subprocess.PIPE)
p.stdin.write('12345\n')
for line in p.stdout: 
    print(">>> " + str(line.rstrip())) 
    p.stdout.flush() 

到目前为止,每当我从 p.stdout 读取时,它总是要等到进程结束后才输出一个空字符串。我尝试了很多方法 - 但结果还是一样。

我试过Python 2.6和3.1,但版本并不重要 - 我只需要在某个地方让它工作。

5 个回答

1

这里的 bufsize=256 参数是用来限制数据发送给子进程的大小的,它确保像 12345\n 这样的数据不会被分成小于256字节的块发送。如果不设置 bufsize 或者在 p.stdin.write() 之后加上 p.stdin.flush(),数据可能会被分成更小的块发送。默认情况下,数据是按行缓冲的。

无论哪种情况,你至少应该能看到一个空行,这个空行是由你示例中的第一个 printf(\n...) 输出的。

1

把从管道里读取的数据放到一个单独的线程中,这样当有一部分输出可用时,就会发出信号通知你:

我怎么才能非阻塞地读取subprocess.Popen.stdout中的所有可用数据?

4

在和子进程进行数据读写时,使用管道会比较复杂,因为默认情况下数据在两个方向上都有缓冲。这很容易导致死锁的情况,比如父进程或子进程在读取一个空的缓冲区,或者在写入一个已经满的缓冲区,或者在等待数据的情况下进行阻塞读取,而这个缓冲区还没有被系统库刷新。

如果数据量不大,Popen.communicate() 方法可能就足够用了。不过,如果数据量超过了它的缓冲能力,你可能会遇到进程停滞的问题(这可能就是你现在看到的情况?)

你可以查查如何使用 fcntl 模块,把你的文件描述符设置为非阻塞模式。这样的话,你就需要在读取和写入这些文件描述符时,添加适当的异常处理来应对“EWOULDBLOCK”事件。(我不记得在Python中具体会抛出什么异常了)。

另外一种完全不同的方法是,让父进程使用 select 模块和 os.fork(),然后让子进程在处理完文件复制后,使用 execve() 来执行目标程序。(基本上你会重新实现 Popen() 的部分功能,但处理父进程的文件描述符(管道)时会有所不同。)

顺便提一下,在Python的2.5和2.6标准库中,.communicate 方法只能处理大约64K的远程数据(在Linux和FreeBSD上)。这个数字可能会因为各种因素而变化(可能包括编译你的Python解释器时使用的构建选项,或者链接的libc版本)。它并不是简单地受到可用内存的限制(尽管J.F. Sebastian曾声称相反),而是限制在一个更小的值上。

撰写回答