Python C 程序子进程在“for line in iter”处挂起

Posted

技术标签:

【中文标题】Python C 程序子进程在“for line in iter”处挂起【英文标题】:Python C program subprocess hangs at "for line in iter" 【发布时间】:2013-12-10 19:38:35 【问题描述】:

好的,我正在尝试从 python 脚本运行 C 程序。目前我正在使用一个测试 C 程序:

#include <stdio.h>

int main() 
    while (1) 
        printf("2000\n");
        sleep(1);
    
    return 0;

模拟我将使用的程序,该程序不断地从传感器获取读数。 然后我试图用python中的子进程从C程序中读取输出(在本例中为"2000"):

#!usr/bin/python
import subprocess

process = subprocess.Popen("./main", stdout=subprocess.PIPE)
while True:
    for line in iter(process.stdout.readline, ''):
            print line,

但这不起作用。从使用打印语句开始,它运行 .Popen 行,然后在 for line in iter(process.stdout.readline, ''): 处等待,直到我按下 Ctrl-C。

这是为什么?这正是我见过的大多数示例的代码,但它不读取文件。

有没有办法让它只在需要阅读的时候运行?

【问题讨论】:

删除while True。一旦for-loop 结束;你不会再从process.stdout 得到任何东西了。 我认为答案应该改为J.F. Sebastians 版本。看看我的评论为什么。 谢谢@Scheintod,好点子。 【参考方案1】:

这是一个块缓冲问题。

以下是我对Python: read streaming input from subprocess.communicate() 问题的回答的扩展版。

直接修复C程序中的stdout缓冲区

基于stdio 的程序通常在终端中交互运行时进行行缓冲,并在其标准输出重定向到管道时进行块缓冲。在后一种情况下,在缓冲区溢出或刷新之前,您不会看到新行。

为避免在每次调用 printf() 后调用 fflush(),您可以在开始时调用 C 程序来强制行缓冲输出:

setvbuf(stdout, (char *) NULL, _IOLBF, 0); /* make line buffered stdout */

在这种情况下,一旦打印了换行符,缓冲区就会被刷新。

或者在不修改C程序源码的情况下修复

stdbuf 实用程序允许您在不修改源代码的情况下更改缓冲类型,例如:

from subprocess import Popen, PIPE

process = Popen(["stdbuf", "-oL", "./main"], stdout=PIPE, bufsize=1)
for line in iter(process.stdout.readline, b''):
    print line,
process.communicate() # close process' stream, wait for it to exit

还有其他可用的实用程序,请参阅Turn off buffering in pipe。

或者使用伪TTY

要诱使子进程认为它正在交互运行,您可以使用pexpect module 或其类似物,有关使用pexpectpty 模块的代码示例,请参阅Python subprocess readlines() hangs。这是那里提供的pty 示例的变体(它应该适用于Linux):

#!/usr/bin/env python
import os
import pty
import sys
from select import select
from subprocess import Popen, STDOUT

master_fd, slave_fd = pty.openpty()  # provide tty to enable line buffering
process = Popen("./main", stdin=slave_fd, stdout=slave_fd, stderr=STDOUT,
                bufsize=0, close_fds=True)
timeout = .1 # ugly but otherwise `select` blocks on process' exit
# code is similar to _copy() from pty.py
with os.fdopen(master_fd, 'r+b', 0) as master:
    input_fds = [master, sys.stdin]
    while True:
        fds = select(input_fds, [], [], timeout)[0]
        if master in fds: # subprocess' output is ready
            data = os.read(master_fd, 512) # <-- doesn't block, may return less
            if not data: # EOF
                input_fds.remove(master)
            else:
                os.write(sys.stdout.fileno(), data) # copy to our stdout
        if sys.stdin in fds: # got user input
            data = os.read(sys.stdin.fileno(), 512)
            if not data:
                input_fds.remove(sys.stdin)
            else:
                master.write(data) # copy it to subprocess' stdin
        if not fds: # timeout in select()
            if process.poll() is not None: # subprocess ended
                # and no output is buffered <-- timeout + dead subprocess
                assert not select([master], [], [], 0)[0] # race is possible
                os.close(slave_fd) # subproces don't need it anymore
                break
rc = process.wait()
print("subprocess exited with status %d" % rc)

或者通过pexpect使用pty

pexpect 包装 pty 处理成 higher level interface:

#!/usr/bin/env python
import pexpect

child = pexpect.spawn("/.main")
for line in child:
    print line,
child.close()

Q: Why not just use a pipe (popen())? 解释了为什么伪 TTY 很有用。

【讨论】:

我认为这个答案应该被标记为正确,因为它是最完整、最简洁的,并提供了各种可能的解决方案和进一步的阅读材料。 (这是一个很好的答案。)但我真的认为,为了让读者处理这些密集的信息,它绝对需要不同方法之间的某种标题。我认为大多数人看到大蟒蛇块并在意识到它只是众多可能的解决方案之一之前被吓跑了。 @Scheintod:我添加了部分标题。老实说:我回答这个问题是因为我想和ptyselect 一起玩。顺便说一句,你可以edit the answer yourself,如果它不会使情况变得明显更糟。 你知道在 Windows 上有什么类似的输出缓冲解决方案吗? @kuchi 我不知道 @kuchi 看看pywinpty。我不知道这是否回答了你的问题,但它似乎做了类似 pty 的事情,但适用于 windows。【参考方案2】:

您的程序没有挂起,它只是运行得很慢。您的程序正在使用缓冲输出; "2000\n" 数据不会立即写入标准输出,但最终会写入。在您的情况下,可能需要 BUFSIZ/strlen("2000\n") 秒(可能需要 1638 秒)才能完成。

在这一行之后:

printf("2000\n");

添加

fflush(stdout);

【讨论】:

非常感谢!这正是问题所在 谢谢 Rob,我只是想补充一下,这样他就能更快地看到它。 @theoB610,请注意您仍然需要换行符。 非常真实!谢谢@codnodder【参考方案3】:

见readline docs。

您的代码:

process.stdout.readline

正在等待 EOF 或换行符。

我不知道你最终想要做什么,但是在你的 printf 中添加一个换行符,例如,printf("2000\n");,至少应该让你开始。

【讨论】:

以上是关于Python C 程序子进程在“for line in iter”处挂起的主要内容,如果未能解决你的问题,请参考以下文章

Linux 中 Python 父进程和 C 子进程之间的通信

在 Windows 上将 ^C 发送到 Python 子进程对象

当父母在python中崩溃时杀死子进程

如何在python中使用trtexec作为子进程

在python子进程中将字符串作为参数发送

如何在子进程python中创建和复制文件的内容