Python中的subprocess模块出现资源暂时不可用错误

4 投票
2 回答
16272 浏览
提问于 2025-04-18 00:23

在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 个回答

1

你应该调用 p.wait() 来等待子进程完成,然后再收集它的结果,这样在和它沟通结束后就能处理它了。

如果你有特殊情况,比如想同时启动多个进程然后再等它们完成,可以使用 p.poll() 来检查某个进程是否已经完成。

因为你已经设置了管道,所以应该使用 p.communicate() 来避免出现死锁的情况。详细信息可以查看 这份文档

4

进程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(),也不应该有太多的僵尸进程(已经结束但状态未读的进程)。

撰写回答