为何Python中subprocess.PIPE配合select时未出现阻塞?
问题场景与解决
问题代码
import os, select, subprocess p1 = subprocess.Popen(["pwd"], stdout = subprocess.PIPE) r, _, _ = select.select([p1.stdout.fileno()], [], []) os.read(r[0], 1024) r, _, _ = select.select([p1.stdout.fileno()], [], []) os.read(r[0], 1024)
问题现象
首次调用select能正确检测到p1.stdout管道有数据,但第二次select同样返回管道可读时,os.read却拿到空字节。需求是让select能阻塞到管道真正有数据时再返回,而非提前返回空可读状态。
运行输出:
>>> import os, select, subprocess >>> p1 = subprocess.Popen(["pwd"], stdout = subprocess.PIPE) >>> r, _, _ = select.select([p1.stdout.fileno()], [], []) >>> os.read(r[0], 1024) b'/tmp\n' >>> r, _, _ = select.select([p1.stdout.fileno()], [], []) >>> os.read(r[0], 1024) b'' >>>
原因分析
子进程pwd执行完毕后会关闭stdout的写端,此时管道读端进入EOF状态。对select来说,这种状态属于“可读”——因为调用read不会阻塞,会直接返回空字节表示数据流结束。这就是第二次select直接返回,但读不到新数据的原因。
解决方案
要实现“让select阻塞到有真实数据时返回”,可以通过以下几种方式处理:
方案1:先检查子进程状态再调用select
在每次select前,先判断子进程是否还存活,若已结束则停止监听:
import os, select, subprocess p1 = subprocess.Popen(["pwd"], stdout=subprocess.PIPE) while True: # 检查子进程是否仍在运行 if p1.poll() is not None: # 读取剩余数据后退出循环 remaining = os.read(p1.stdout.fileno(), 1024) if remaining: print(remaining) break # 子进程存活时才调用select等待数据 r, _, _ = select.select([p1.stdout.fileno()], [], []) data = os.read(r[0], 1024) if data: print(data) else: break
方案2:读取到空字节时终止监听
当os.read返回空字节时,说明管道已到EOF,直接停止后续的select调用:
import os, select, subprocess p1 = subprocess.Popen(["pwd"], stdout=subprocess.PIPE) # 第一次读取数据 r, _, _ = select.select([p1.stdout.fileno()], [], []) data = os.read(r[0], 1024) print(data) # 第二次读取前判断是否已到EOF if data: r, _, _ = select.select([p1.stdout.fileno()], [], []) data = os.read(r[0], 1024) if not data: print("管道已关闭,无新数据")
方案3:用subprocess内置方法替代手动select
如果不需要手动控制select的复杂逻辑,直接用communicate()就能自动处理子进程输出读取和等待:
import subprocess p1 = subprocess.Popen(["pwd"], stdout=subprocess.PIPE) stdout, _ = p1.communicate() print(stdout)
内容的提问来源于stack exchange,提问作者chrislamp
相关产品推荐
相关产品推荐

