subprocess.Popen调用阻塞咨询:卡在stdout.read(1)处
解决Popen监控子进程输出时卡在read()的问题
我之前也碰到过一模一样的坑!你这个问题大概率是管道缓冲区阻塞或者未处理stderr导致的进程挂起,咱们一步步来解决:
核心原因分析
- 缓冲机制问题:当子进程通过管道输出时,默认是全缓冲或行缓冲模式——也就是说,子进程要输出满缓冲区(通常4k/8k)或者遇到换行符,才会把内容推到管道里。你的
read(1)会一直等,直到有内容过来。 - stderr未处理阻塞:如果子进程往stderr输出内容,但你只监听了stdout,当stderr的管道缓冲区被填满时,子进程会卡住无法继续执行,自然也不会往stdout输出任何东西。
针对性解决方案
方案1:重定向stderr+设置无缓冲
最简单的办法是把stderr重定向到stdout,同时设置bufsize=0让输出无缓冲,这样子进程一输出就能被读到:
from subprocess import Popen, PIPE, STDOUT cmd = "/usr/bin/App.out" # bufsize=0 开启无缓冲,stderr=STDOUT 合并错误输出到标准输出 proc = Popen(cmd, shell=True, stdin=PIPE, stdout=PIPE, stderr=STDOUT, bufsize=0) c = proc.stdout.read(1) print(c.decode(), end='') # Python3记得解码,Python2直接print就行
方案2:用线程单独处理stderr(适合需要区分stdout/stderr的场景)
如果需要分别处理标准输出和错误输出,开个后台线程读取stderr就能避免阻塞:
from subprocess import Popen, PIPE import threading def watch_stderr(proc): """后台线程读取stderr""" while True: data = proc.stderr.read(1) if not data: break print(f"[错误输出] {data.decode()}", end='') cmd = "/usr/bin/App.out" proc = Popen(cmd, shell=True, stdin=PIPE, stdout=PIPE, stderr=PIPE, bufsize=0) # 启动后台线程监控stderr stderr_thread = threading.Thread(target=watch_stderr, args=(proc,)) stderr_thread.daemon = True # 随主进程退出 stderr_thread.start() # 读取stdout c = proc.stdout.read(1) print(f"[标准输出] {c.decode()}", end='')
方案3:用select实现多流监听(适合实时交互场景)
如果需要根据子进程的输出动态输入内容,用select模块可以同时监听stdout和stderr,避免单流读取阻塞:
from subprocess import Popen, PIPE import select cmd = "/usr/bin/App.out" proc = Popen(cmd, shell=True, stdin=PIPE, stdout=PIPE, stderr=PIPE, bufsize=0) while proc.poll() is None: # 子进程还在运行时循环监听 # 监听stdout和stderr,超时1秒避免死等 ready_streams, _, _ = select.select([proc.stdout, proc.stderr], [], [], 1) for stream in ready_streams: data = stream.read(1) if not data: continue if stream == proc.stdout: print(f"[OUT] {data.decode()}", end='') # 示例:如果输出是问号,就给子进程输入响应 if data == b'?': proc.stdin.write(b"my_response\n") proc.stdin.flush() # 必须flush,不然输入会留在缓冲区 else: print(f"[ERR] {data.decode()}", end='')
额外建议:尽量不用shell=True
如果你的命令没有用到shell的特性(比如通配符、管道),直接传命令列表替代shell=True,能避免shell层的缓冲问题,更稳定:
# 直接传列表,不用shell proc = Popen(["/usr/bin/App.out"], stdin=PIPE, stdout=PIPE, stderr=PIPE, bufsize=0)
内容的提问来源于stack exchange,提问作者Deepak Nayak
相关产品推荐
相关产品推荐

