Python中的subprocess模块出现资源暂时不可用错误
在Python中,我启动了一个gnuplot
进程,用来从数据集中生成gif图片。
from subprocess import Popen, PIPE
def gnuplotter(...)
p = Popen([GNUPLOT], shell=False, stdin=PIPE, stdout=PIPE)
p.stdin.write(r'set terminal gif;')
...
p.stdin.write(contents)
p.stdout.close()
当我只使用一次gnuplotter()
时,一切都很顺利,但当我多次启动这个进程时,我遇到了Resource temporarily unavailable
的错误。
for i in range(54):
gnuplotter(i, ...
File "/Users/smcho/code/PycharmProjects/contextAggregator/aggregation_analyzer/aggregation_analyzer/gnuplotter.py", line 48, in gnuplotter
p = Popen([GNUPLOT], shell=False, stdin=PIPE, stdout=PIPE)
File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/subprocess.py", line 711, in __init__
errread, errwrite)
File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/subprocess.py", line 1205, in _execute_child
self.pid = os.fork()
OSError: [Errno 35] Resource temporarily unavailable
这是什么问题,我该如何在启动另一个进程之前关闭gnuplot进程呢?
2 个回答
你应该调用 p.wait()
来等待子进程完成,然后再收集它的结果,这样在和它沟通结束后就能处理它了。
如果你有特殊情况,比如想同时启动多个进程然后再等它们完成,可以使用 p.poll()
来检查某个进程是否已经完成。
因为你已经设置了管道,所以应该使用 p.communicate()
来避免出现死锁的情况。详细信息可以查看 这份文档。
进程ID、打开的文件描述符和内存都是有限的资源。
fork(2) 手册提到,当出现 errno.EAGAIN
时的情况:
[EAGAIN] The system-imposed limit on the total number of processes under execution would be exceeded. This limit is configuration-dependent. [EAGAIN] The system-imposed limit MAXUPRC () on the total number of processes under execution by a single user would be exceeded.
为了更容易重现这个错误,你可以在程序开始时添加:
import resource
resource.setrlimit(resource.RLIMIT_NPROC, (20, 20))
问题可能是因为所有的子进程都还在运行,因为你没有调用 p.stdin.close()
,而且当 gnuplot 的标准输入被重定向到管道时,它的输入可能会被完全缓存,也就是说,gnuplot
进程可能在等待输入而卡住了。还有可能是你的应用程序使用了太多的文件描述符(在 Python 2.7 中,文件描述符默认是被子进程继承的),而没有释放它们。
如果输入不依赖于输出,并且输入的大小是有限的,那么可以使用 .communicate()
:
from subprocess import Popen, PIPE, STDOUT
p = Popen("gnuplot", stdin=PIPE, stdout=PIPE, stderr=PIPE,
close_fds=True, # to avoid running out of file descriptors
bufsize=-1, # fully buffered (use zero (default) if no p.communicate())
universal_newlines=True) # translate newlines, encode/decode text
out, err = p.communicate("\n".join(['set terminal gif;', contents]))
.communicate()
会写入所有输入并读取所有输出(同时进行,所以不会出现死锁),然后关闭 p.stdin、p.stdout 和 p.stderr(即使输入很小,gnuplot 的那边也被完全缓存;EOF 会刷新缓存),并等待进程完成(没有僵尸进程)。
Popen
在构造函数中调用 _cleanup()
,这个方法会 检查所有已知子进程的退出状态,也就是说,即使你不调用 p.wait()
,也不应该有太多的僵尸进程(已经结束但状态未读的进程)。