Windows下如何非阻塞读取Python subprocess Popen的标准输出?
问题分析与解决方案
这个问题我在Windows平台上处理subprocess时也踩过坑,核心问题出在输出缓冲机制上,结合Windows管道的特性,直接导致了父进程的阻塞。
为什么会阻塞?
你的test2.py里用print()输出内容,但Python的print在非终端环境(比如通过管道输出给父进程)时,默认是块缓冲模式——也就是说,子进程的输出会先攒在缓冲区里,只有当缓冲区被填满,或者子进程终止时,才会把内容发送到管道里。这就解释了为什么你让test2只输出一个数就终止时父进程能正常读取:因为进程终止时会强制刷新缓冲区。
而你在test1里用proc.stdout.read(1),父进程会一直等待管道里有数据过来,但子进程的输出被卡在缓冲区里没发出来,自然就永久阻塞了。
解决方案
这里给你几个可行的方案,按推荐程度排序:
1. 修改子进程的输出,强制刷新缓冲
直接在test2.py的print语句里加上flush=True参数,让每次打印都立刻把内容输出到管道:
import random import time def r(): while True: yield random.randint(0, 100) for i in r(): print(i, flush=True) # 关键:强制刷新缓冲区 time.sleep(1)
这样修改后,test1里的read(1)或者逐行读取都能实时拿到子进程的输出。
2. 启动子进程时开启Python无缓冲模式
如果你不想修改test2的代码,可以在启动子进程时设置PYTHONUNBUFFERED环境变量,让子进程的Python解释器禁用输出缓冲:
import subprocess as sbp import os # 复制当前环境变量,添加PYTHONUNBUFFERED设置 env = dict(os.environ, PYTHONUNBUFFERED='1') with sbp.Popen('python tests/test2.py', stdout=sbp.PIPE, env=env) as proc: print('parent process') # 改成逐行读取更方便,避免read(1)拿到部分字符 for line in proc.stdout: print(f"收到子进程输出: {line.decode('utf-8').strip()}") print('end.')
这个方法的好处是不需要改动子进程代码,适合子进程无法修改的场景。
3. 父进程用非阻塞方式读取(进阶)
如果上面的方法都不适用,你可以用Windows的API来设置管道为非阻塞模式,不过这个方法比较复杂,需要用到ctypes调用系统函数,一般推荐用前面两种更简单的方案。
为什么你之前的尝试没用?
communicate()本身就是等待子进程终止后再读取所有输出,所以必然会阻塞到子进程结束;poll()只是检测子进程是否还在运行,但read()依然会等待管道中有数据,而子进程的缓冲没刷新的话,管道里还是没数据,所以还是会阻塞。
内容的提问来源于stack exchange,提问作者Shannon
相关产品推荐
相关产品推荐

