Python如何获取超时终止的永久运行子进程的输出
问题说明
需要启动一个设计为永久运行、不会自行退出的子进程,控制其运行满10秒后,获取这段时间内产生的所有输出。
初始实现代码如下,触发超时后无法正确读取输出:
import subprocess try: process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) process.wait(timeout=10) except subprocess.TimeoutExpired: print('Timeout reached')
直接打印process.stderr和process.stdout只能拿到BufferedReader对象信息,得不到实际输出内容:
print(process.stderr) # <_io.BufferedReader name=11> print(process.stdout) # <_io.BufferedReader name=8>
直接调用process.stderr.read()、process.stdout.read()会出现永久阻塞的问题。
故障原因
process.wait(timeout=10)仅会等待进程退出,触发TimeoutExpired异常时子进程并没有被自动终止,仍在后台运行- 管道的
read()方法默认会一直阻塞,直到读取到管道写端发送的EOF信号。只要子进程还在运行、没有关闭管道写端,EOF就不会产生,因此读取操作会永久卡住 - 如果子进程输出量超过操作系统管道缓冲区上限(通常为64KB),子进程会因为写缓冲区满被阻塞,连
wait()调用本身都会出现死锁。
实现方案
方案一:一次性获取全量输出(推荐)
使用subprocess自带的communicate()方法,该方法会在后台持续读取stdout、stderr的内容,不会出现管道缓冲区满死锁的问题。超时触发后先终止子进程,再读取缓冲区中剩余的所有输出即可:
import subprocess # 初始化子进程 process = subprocess.Popen( command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True # 配置该参数直接返回字符串格式输出,不配置则返回字节串 ) stdout_res = "" stderr_res = "" try: # 等待10秒,同时持续读取输出 stdout_res, stderr_res = process.communicate(timeout=10) except subprocess.TimeoutExpired: # 超时后终止子进程 process.kill() # 终止后再次调用communicate,读取缓冲区中剩余的输出 stdout_res, stderr_res = process.communicate() # 打印结果 print("标准输出:\n", stdout_res) print("标准错误:\n", stderr_res)
注意:如果使用
shell=True启动进程,直接调用kill()仅会终止shell进程本身,不会终止shell拉起的业务子进程,会造成后台进程残留。如果需要彻底清理进程,可以在Popen初始化时增加start_new_session=True参数,超时后调用os.killpg(process.pid, signal.SIGKILL)杀掉整个进程组。
方案二:实时读取输出
如果需要在子进程运行过程中实时获取输出(比如边运行边打印日志),可以采用逐行读取的方式,运行满10秒后主动终止进程:
import subprocess import time process = subprocess.Popen( command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, # 将标准错误合并到标准输出,无需单独读取两个管道 text=True, bufsize=1 # 配置行缓冲,支持逐行读取 ) start_ts = time.time() output_collect = [] # 运行满10秒 while time.time() - start_ts < 10: line = process.stdout.readline() if not line: break output_collect.append(line) # 可在此处实时打印当前行输出 print(line.strip()) # 到点终止进程,回收资源 process.kill() process.wait() full_output = "".join(output_collect) print("10秒内累计输出:\n", full_output)
内容的提问来源于stack exchange,提问作者user1315621
相关产品推荐
相关产品推荐

