python subprocess.Popen read阻塞问题解决
背景
使用subprocess.Popen打开一个子进程,指定子进程的标准输入,标准输出为subprocess.PIPE,使用stdout.read()读取子进程的标准输出,当子进程没有输出时read会导致程序阻塞。
原因分析
指定标准输入,标准输出,标准错误为subprocess.PIPE后,实则是使用匿名管道和子进程进行通信。匿名管道有两个非常重要的特性:
1.当写端一直写,而读端不去读,管道被写满后,写端继续写就会阻塞;
2.当写端打开不写数据,管道为空时,读端去读就会阻塞。
我们遇到的问题就是由上面第二个原因造成的。
解决思路
- 使用Popen.communicate()
在python的官方文档中提到为了避免管道缓冲被填满导致阻塞,可以使用Popen.communicate()来规避,并且可以传入timeout参数。
缺点:communicate()后便会关闭输入管道,无法和子进程继续交互,并且不是实时获取子进程输出。 - 使用非阻塞方式进行读取
网上查阅一番资料后,看到的使用非阻塞方式读取只在linux下有效。 - 从匿名管道本身入手
查看了windows关于匿名管道的api后发现了PeekNamedPipe()这个函数
使用改函数可以查看管道中是否有数据,当存在数据时再去进行读取,就可以避免由于管道没有数据造成的阻塞。
最终,示例代码如下:
import _winapi
import os
import subprocess
import time
SIZE = 128
r_stdin,w_stdin = _winapi.CreatePipe(None, SIZE)
r_stdout,w_stdout = _winapi.CreatePipe(None, SIZE)
w_stderr = w_stdout
os.set_handle_inheritable(r_stdin, True)
os.set_handle_inheritable(w_stdout, True)
os.set_handle_inheritable(w_stderr, True)
startupinfo = subprocess.STARTUPINFO()
startupinfo.dwFlags |= subprocess.STARTF_USESTDHANDLES
startupinfo.hStdInput = r_stdin
startupinfo.hStdOutput = w_stdout
startupinfo.hStdError = w_stderr
startupinfo.lpAttributeList['handle_list'] = [w_stderr, w_stdout, r_stdin]
p = subprocess.Popen("adb shell -x", startupinfo = startupinfo)
_winapi.WriteFile(w_stdin, b"ls -l\n", True)
while True:
try_read = _winapi.PeekNamedPipe(r_stdout, SIZE)
if try_read[1] > 0:
data = _winapi.ReadFile(r_stdout, try_read[1], True)
if data[0].GetOverlappedResult(r_stdout):
print(data[0].getbuffer())
time.sleep(0.1)
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)