Python3.6执行subprocess报错Too many open files 设置ulimit无效如何解决
subprocess报Too many open files问题排查与解决
排查步骤
- 先确认当前进程的文件描述符使用情况:执行
ls /proc/<Python进程PID>/fd | wc -l统计打开的fd总数,和当前进程的ulimit上限cat /proc/<Python进程PID>/limits | grep "Max open files"对比,确认是实际使用量超过上限导致的报错。 - 排查泄漏的fd类型:执行
ls -l /proc/<Python进程PID>/fd,如果大部分fd指向pipe类型,即可确认是subprocess创建的管道资源未正确释放导致的泄漏,和你当前的代码写法直接相关。 - 排查ulimit配置生效范围:永久配置不生效通常是因为程序启动方式的问题,
/etc/security/limits.conf仅对PAM登录的会话生效,如果你是用systemd管理的服务、桌面端快捷方式启动的程序,不会读取该配置。可以临时在启动Python程序的终端先执行ulimit -n 65535再启动程序,验证是否是ulimit上限不够的问题。
核心问题原因
你当前的subprocess写法存在明确的资源泄漏风险:
- 仅手动关闭了stdout流,subprocess创建的stderr流、内核层面的errpipe通信管道都没有被回收。
- 调用
terminate()杀进程后没有等待进程退出回收,会产生僵尸进程,对应的管道资源会被内核一直持有不会释放。 - Python3.6版本的subprocess自动回收机制不完善,手动管理资源极易出现遗漏。
解决方法
1. 替换为更安全的subprocess调用方式
优先使用subprocess.run()替代手动管理Popen,它会自动等待进程结束、回收所有关联资源,你的代码可以直接替换为如下写法:
# check参数控制是否要在进程非0退出时抛出异常,可根据自己的业务需求调整 result = subprocess.run(command1, shell=True, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, close_fds=True) output = result.stdout.readlines() # 不需要手动关闭流、杀进程,run方法会自动处理所有资源回收
如果必须使用Popen做异步交互,一定要用上下文管理器托管生命周期:
with subprocess.Popen(command1, shell=True, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, close_fds=True) as process: output = process.stdout.readlines() # 出with作用域后会自动关闭所有流、等待进程退出,无需手动调用terminate和close方法
2. 强制关闭继承的文件描述符
所有subprocess调用都显式加上close_fds=True参数,会在子进程启动时关闭所有从父进程继承的不必要的文件描述符,避免父进程的打开文件被带到子进程中泄漏。
3. 原有写法的兜底修复
如果暂时不能重构现有代码,要在原有逻辑中补充管道回收和进程等待逻辑:
process = subprocess.Popen(command1, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) output = process.stdout.readlines() # 读空stderr缓存,避免缓冲区占着资源 err_output = process.stderr.read() # 显式关闭所有打开的流 process.stdout.close() process.stderr.close() process.terminate() # 等待进程完全退出,回收进程资源 process.wait()
4. 正确配置ulimit上限
如果是systemd管理的服务,修改对应的service配置文件,在[Service]段添加LimitNOFILE=65535,之后执行systemctl daemon-reload && systemctl restart <你的服务名>生效。如果是终端直接运行的脚本,可以在启动脚本的开头添加ulimit -n 65535再启动Python进程。
内容的提问来源于stack exchange,提问作者FIIIBS
相关产品推荐
相关产品推荐

