subprocess.Popen()调用性能随进程数量上升而下降的技术咨询
关于subprocess.Popen()调用耗时随进程数量线性增长的问题
我最近做了个应用,靠subprocess.Popen()来运行和管理一堆长期在线的服务——这些服务会一直跑,直到收到明确的停止指令。但跑着跑着发现个头疼的事儿:随着仲裁程序生成的进程数越来越多,每次调用subprocess.Popen()的返回时间居然跟着近似线性地变长。
我的基础代码大概是这样的:
process_list = [] for command in command_list: start_tm = time.time() process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) end_tm = time.time() # 这里有统计调用耗时的相关逻辑
可能的原因分析
这种线性增长的耗时,大概率和这几个点有关:
- 管道缓冲区阻塞:你用了
subprocess.PIPE捕获stdout和stderr,但如果之前启动的进程输出没被及时读取,管道缓冲区会被占满,新进程的启动就会被间接阻塞,拖慢Popen的返回速度。 - 串行启动的累积开销:循环里逐个启动进程,每个进程的初始化(资源分配、进程上下文切换)开销会累加起来,进程越多,总耗时自然线性上升。
- 系统资源接近上限:系统对进程数、文件描述符(管道会占用文件描述符)有硬性限制,当数量接近上限时,系统创建新进程的开销会大幅增加。
解决思路和方案
针对这些问题,你可以试试下面这些办法:
别随便用PIPE,及时处理输出
如果不需要实时获取子进程的输出,完全可以把输出重定向到文件或者直接丢弃,避免管道缓冲区阻塞的问题:# 重定向到日志文件 process = subprocess.Popen(cmd, stdout=open('service.log', 'a'), stderr=subprocess.STDOUT) # 或者直接丢弃输出 process = subprocess.Popen(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)如果确实需要处理输出,记得在启动子进程后用线程去异步读取stdout和stderr,别让缓冲区堵着。
并行启动进程
别再串行循环逐个启动了,用线程池或者进程池来并行创建子进程,把线性的耗时摊平。比如用concurrent.futures.ThreadPoolExecutor:from concurrent.futures import ThreadPoolExecutor import subprocess import time def start_single_process(cmd): start_tm = time.time() # 这里换成你需要的输出处理方式 process = subprocess.Popen(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) end_tm = time.time() print(f"启动进程耗时: {end_tm - start_tm:.2f}s") return process process_list = [] # 根据系统资源调整max_workers的数值 with ThreadPoolExecutor(max_workers=10) as executor: process_list = list(executor.map(start_single_process, command_list))检查并调整系统资源限制
可以先看看系统的进程数、文件描述符上限:比如在Linux上用ulimit -u看最大进程数,ulimit -n看文件描述符上限。如果当前数值接近上限,适当调大这些参数(需要root权限),能减少系统在资源分配上的开销。改用专业的进程管理工具
如果你的服务是长期运行的,其实没必要自己用subprocess手动管理,像supervisor这类专业的进程管理器,在进程创建、资源复用、故障重启上都做了优化,能省不少事儿,也能避免手动管理带来的性能问题。
内容的提问来源于stack exchange,提问作者Valdogg21
相关产品推荐
相关产品推荐

