Python子进程:打开太多文件

79

我正在使用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

代码:

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()

1
Communicate() 关闭了管道,所以这不是你的问题。最终,Popen() 只是在你用完管道时运行的命令...问题可能出现在你的代码中其他文件没有关闭。我注意到 "SelfE.append" ... 你是否打开了其他文件并将它们保存在列表中? - tdelaney
在运行Python脚本之前,您尝试过执行ulimit -Sn unlimited吗? - Charlie Parker
11个回答

67
在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

5
自2019年10月(El Capitan 10.11.6)以来,ulimit -a 的输出略有不同,例如 -n 现在是“file descriptors”而不是“open files” -n:file descriptors。但是 ulimit -Sn 50000 解决了我的问题。谢谢。 - Wlad
1
为什么不使用 ulimit -Sn unlimited 呢? - Charlie Parker
1
赞同上面的评论。ulimit -Sn unlimited 是有意义的,但它是否会对阻塞其他系统进程或类似的事情造成太大的危险呢? - benjamin deworsop

28

我猜问题是因为我正在使用subprocess处理一个打开的文件:

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在该文件上调用系统命令。在多次执行此操作后,程序崩溃并显示该错误消息。

因此我从中得出的教训是:

先关闭你所创建的文件,然后再对其进行处理。


21
你可以尝试提高操作系统的打开文件限制:

7
实际上,该命令不会将限制值提高到超过/etc/security/limits.conf文件中设置的值。如果要提高限制值,需要将类似这样的行放置在该文件中: * soft nofile 4096 / * hard nofile 4096(将 4096 替换为你自己的值)。 - Dan D.
1
昨天遇到了这个问题,我不得不在Ubuntu中编辑/etc/security/limits.conf并通过ulimit -n提高限制来克服这个错误。 - Chris J. Vargo

11

正如其他人所指出的,我个人也遇到了在/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脚本的软件也应用了其限制,这些限制覆盖了系统范围的设置。

在解决了我的特定错误后,在此发布此答案,希望能帮助其他人。


6

@Sensei,它确实有效:在父进程中打开文件(确保fds可继承),然后使用close_fds=False生成子进程(这两个在旧版Python中都是默认值,请参阅链接)。看看你会多快地得到错误。显然,close_fds不能在一般情况下防止错误:你甚至不需要生成新的进程就能得到它。 - jfs
除了它并没有起作用。我运行了一个简单的for循环,并生成了足够多的子进程来达到操作系统的限制。我使用close_fds=True进行了尝试,但它没有任何影响。我可能对原因有所误解,但我的猜测是,这个解决方案只适用于生成少量子进程并且从不清理描述符的情况。在这种情况下,这个参数是有意义的,但如果你真的想要同时生成和运行那么多进程,我认为它不会起作用。 - Sensei
1
@Sensei:我知道它能工作,因为stdlib中有测试可以测试这个选项(即,我知道它不仅对我有效)。现在,你的代码可能不像你期望的那样工作。在这种情况下,请创建一个最小但完整的代码示例,逐步描述您期望的确切行为以及发生的情况,并将其作为单独的SO问题发布(提及操作系统,Python版本)。 - jfs
@cglacet:如果你运行test_close_fds(),会发生什么? - jfs
发现这个问题:https://bugs.python.org/issue36432(https://bugs.python.org/issue34602),我会尝试找出在3.7.3下运行测试的问题,并尽快给出结果。 - cglacet
显示剩余7条评论

5

请改用上下文管理器:

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.stdoutp.stderr

Python相关代码:https://github.com/python/cpython/blob/208a7e957b812ad3b3733791845447677a704f3e/Lib/subprocess.py#L1031-L1038

相关文档:https://docs.python.org/zh-cn/3/library/subprocess.html#subprocess.Popen


5
也许你正在多次调用该命令。如果是这样,每次都会执行 stdout=subprocess.PIPE。在每次调用之间,请尝试执行 p.stdout.close()

3
如果您正在使用Linux,可以轻松调试此问题。
1- 在终端中启动最终会因“打开的文件过多”而失败的命令。 python -m module.script 2- 让它运行一段时间(这样它就可以开始打开实际的文件),每当您认为它已经这样做时,只需按下CTRL+Z,以便挂起该进程。您将获得一个带有进程ID的输出。
^Z
[2]  + 35245 suspended  python -m module.script

35245 是您的进程ID。

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

1
感谢您提供使用PID查看打开文件的命令,非常有用。但是,我对您的代码片段有一个问题:使用withopen的整个重点不就是它会在块结束时自动关闭文件吗?为什么您要在with块内显式地关闭它呢? - lotif
1
@Iot如果你是对的。withopen应该在块结束时关闭文件。我需要再次测试以检查,但像这个答案https://dev59.com/rloT5IYBdhLWcg3wkAE9#50113736肯定会创建问题。 - Tk421

2
将限制提高到例如32768,请在/etc/security/limits.conf中添加以下行:
* soft nofile 32768
* hard nofile 32768

然后,也要运行 ulimit -n 32768

来源:Dan D.的评论


0
我在/etc/security/limits.conf中提高了限制,但我的Python进程仍然只有最大1024个打开文件的限制。
我的Python进程作为一个服务运行,结果发现systemd默认注入了1024,并因此覆盖了limits.conf中的设置。我需要将以下内容添加到/etc/systemd/system/my.service中:
[Service]
LimitNOFILE=327680

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接