Python 子进程:打开的文件太多
Posted
技术标签:
【中文标题】Python 子进程:打开的文件太多【英文标题】:Python Subprocess: Too Many Open Files 【发布时间】:2013-05-07 18:23:21 【问题描述】:我正在使用 subprocess 调用另一个程序并将其返回值保存到一个变量中。这个过程在循环中重复,几千次后程序崩溃并出现以下错误:
Traceback (most recent call last):
File "./extract_pcgls.py", line 96, in <module>
SelfE.append( CalSelfEnergy(i) )
File "./extract_pcgls.py", line 59, in CalSelfEnergy
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True)
File "/usr/lib/python3.2/subprocess.py", line 745, in __init__
restore_signals, start_new_session)
File "/usr/lib/python3.2/subprocess.py", line 1166, in _execute_child
errpipe_read, errpipe_write = _create_pipe()
OSError: [Errno 24] Too many open files
非常感谢任何想法如何解决这个问题!
由 cmets 提供的代码:
cmd = "enerCHARMM.pl -parram=x,xtop=topology_modified.rtf,xpar=lipid27_modified.par,nobuildall -out vdwaals 0".format(cmtup[1])
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True)
out, err = p.communicate()
【问题讨论】:
Communicate() 关闭管道,所以这不是你的问题。最后, Popen() 只是当您用完管道时碰巧运行的命令......问题可能出在您的代码中的其他地方,而其他文件处于打开状态。我注意到“SelfE.append”...您是否打开其他文件并将它们保存在列表中? 在运行 python 脚本之前,您是否尝试过ulimit -Sn unlimited
?
【参考方案1】:
在 Mac OSX (El Capitan) 中查看当前配置:
#ulimit -a
core file size (blocks, -c) 0
data seg size (kbytes, -d) unlimited
file size (blocks, -f) unlimited
max locked memory (kbytes, -l) unlimited
max memory size (kbytes, -m) unlimited
open files (-n) 256
pipe size (512 bytes, -p) 1
stack size (kbytes, -s) 8192
cpu time (seconds, -t) unlimited
max user processes (-u) 709
virtual memory (kbytes, -v) unlimited
将打开文件值设置为10K:
#ulimit -Sn 10000
验证结果:
#ulimit -a
core file size (blocks, -c) 0
data seg size (kbytes, -d) unlimited
file size (blocks, -f) unlimited
max locked memory (kbytes, -l) unlimited
max memory size (kbytes, -m) unlimited
open files (-n) 10000
pipe size (512 bytes, -p) 1
stack size (kbytes, -s) 8192
cpu time (seconds, -t) unlimited
max user processes (-u) 709
virtual memory (kbytes, -v) unlimited
【讨论】:
ulimit -a
的输出与 2019 年十月(El Capitan 10.11.6)略有不同,例如-n 现在是“文件描述符”而不是“打开文件”-n: file descriptors
。但是ulimit -Sn 50000
解决了我的问题。谢谢。
为什么不ulimit -Sn unlimited
?【参考方案2】:
我猜这个问题是由于我正在使用子进程处理一个打开的文件:
cmd = "enerCHARMM.pl -par param=x,xtop=topology_modified.rtf,xpar=lipid27_modified.par,nobuildall -out vdwaals 0".format(cmtup[1])
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True)
这里的 cmd 变量包含一个刚刚创建但尚未关闭的文件的名称。然后subprocess.Popen
对该文件调用系统命令。多次执行此操作后,程序崩溃并显示该错误消息。
所以我从中学到的信息是
关闭你创建的文件,然后处理它
【讨论】:
【参考方案3】:您可以尝试提高操作系统的打开文件限制:
ulimit -n 2048
【讨论】:
实际上该命令不会将限制提高到超过/etc/security/limits.conf
中设置的限制。要提高它,您需要在该文件中放置类似 * soft nofile 4096
/ * hard nofile 4096
的行(将 4096
替换为您自己的值)。
昨天遇到了这个问题,我不得不在 ubuntu 中同时编辑 /etc/security/limits.conf
并通过 ulimit -n
提高限制来克服这个错误。【参考方案4】:
正如其他人所指出的,提高 /etc/security/limits.conf 中的限制以及文件描述符对我个人来说是一个问题,所以我这样做了
sudo sysctl -w fs.file-max=100000
并添加到/etc/sysctl.conf:
fs.file-max = 100000
重新加载:
sudo sysctl -p
此外,如果您想确保您的流程不受其他任何因素(我的流程受到影响)的影响,请使用
cat /proc/process id/limits
要找出你的进程的实际限制是什么,对我来说,运行 python 脚本的软件也应用了它的限制,这些限制已经覆盖了系统范围的设置。
在解决了我的这个错误的特定问题后在这里发布这个答案,希望它可以帮助某人。
【讨论】:
【参考方案5】:Popen()
创建的子进程可以从父进程继承打开的文件描述符(有限资源)。在 POSIX 上使用 close_fds=True
(自 Python 3.2 以来的默认值)来避免它。另外,"PEP 0446 -- Make newly created file descriptors non-inheritable" deals with some remaining issues (since Python 3.4)。
【讨论】:
我认为这行不通,至少在所有情况下都是这样。我在一个具有 1024 个打开文件限制(Ubuntu 上的默认设置)的系统上生成了 1200 个睡眠生成进程,即使使用 close_fds=True 也会爆炸。所以我认为还有更多。因为无论如何你仍然有超过打开进程的限制,这只有在你假设问题出在留下打开文件描述符的完成进程中才有效。 @Sensei 确实有效:在父进程中打开文件(确保 fds 是可继承的)然后使用close_fds=False
生成子进程(两者都是旧 Python 版本的默认设置,请点击链接)。看看你多久会得到错误。显然close_fds
无法阻止一般情况下的错误:您甚至不需要生成一个新进程来获取它。
除非它没有。我运行了一个简单的 for 循环并生成了足够的子进程来达到操作系统的限制。我用 close_fds=True 做到了这一点。它没有影响。为什么我可能错了,但我的猜测很简单,这个解决方案只有在你产生一些子进程并且从不清理描述符的情况下才有效。在这种情况下,这个论点是有道理的,但如果你真的打算一次生成并运行这么多进程,我认为它不起作用。
@Sensei:我知道它可以工作,因为 stdlib 中有执行此选项的测试(即,我知道它不仅对我有用)。现在,您的代码可能无法按预期工作。在这种情况下,创建一个最小但完整的代码示例,逐步描述您期望的确切行为以及发生的情况,并将其作为单独的 SO 问题发布(提及操作系统,Python 版本)。
我认为你们两个之间存在误解,我猜老师所说的是,如果您生成(并且不终止)许多进程,它仍然会崩溃。而我认为你说的是如果你产生许多子进程(这将在某个时候终止)那么这个解决方案就有效。我有一个案例,我刚刚运行了许多 asyncio.create_subprocess_exec
(其中大部分是按顺序运行的,最多同时打开 10 个)并且我仍然有一个“错误”,当我查看我的脚本打开了多少描述符时,数字是远高于10,高得多。我正在尝试你的想法。【参考方案6】:
也许您正在多次调用该命令。如果是这样,每次你在做stdout=subprocess.PIPE
。在每次通话之间尝试做p.stdout.close()
。
【讨论】:
【参考方案7】:改用上下文管理器:
cmd = "enerCHARMM.pl -param=x,xtop=topology_modified.rtf,xpar=lipid27_modified.par,nobuildall -out vdwaals 0".format(cmtup[1])
with subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True) as p:
out, err = p.communicate()
这将在最后一行之后关闭p.stdout
和p.stderr
。
Python中相关代码:https://github.com/python/cpython/blob/208a7e957b812ad3b3733791845447677a704f3e/Lib/subprocess.py#L1031-L1038
相关文档:https://docs.python.org/3/library/subprocess.html#subprocess.Popen
【讨论】:
【参考方案8】:如果您在 Linux 上工作,您可以轻松调试此问题
1 - 启动最终会因终端中打开的文件过多而失败的命令。
python -m module.script
2 - 让它运行一段时间(这样它就可以开始打开实际文件),只要你相信它已经这样做了,只需按CTRL+Z
,这个过程就会暂停。您将获得带有进程 ID 的输出。
^Z
[2] + 35245 suspended python -m module.script
35245
是您的 PID。
3 - 现在您可以检查哪些文件实际打开和未关闭。
ls -alht /proc/35245/fd/
就我而言,我所做的事情与原始帖子非常相似,但在添加一些数据并实际运行 subprocess.Popen
之前,我使用 tempfile.mkstemp()
创建了一个临时文件。
在这种情况下,您需要关闭文件两次,一次是添加信息,第二次是由于mkstemp
fd, path = tempfile.mkstemp()
with open(path, "wb") as f:
f.write(bytes('my data', encoding='utf8'))
f.close() # this is one time
process = subprocess.Popen("my command that requires the previous file" ,[...])
os.close(fd) # this is second time and the one I missed
【讨论】:
【参考方案9】:在子进程中打开文件。它正在阻塞呼叫。
ss=subprocess.Popen(tempFileName,shell=True)
ss.communicate()
【讨论】:
以上是关于Python 子进程:打开的文件太多的主要内容,如果未能解决你的问题,请参考以下文章