理解Python的fork和内存分配错误

10 投票
1 回答
7111 浏览
提问于 2025-04-17 19:16

我有一个内存使用量很大的Python应用程序(占用几百MB到几GB不等)。
这个主要应用需要运行几个非常小的Linux可执行文件,比如:

child = Popen("make html", cwd = r'../../docs', stdout = PIPE, shell = True)
child.wait()

当我使用subprocess.Popen来运行这些外部工具(在长时间的主进程运行结束时运行一次)时,有时会出现OSError: [Errno 12] Cannot allocate memory的错误。
我不明白为什么……请求的进程那么小!
系统有足够的内存可以运行更多的shell。

我使用的是Linux(Ubuntu 12.10,64位),所以我猜subprocess调用了Fork。
Fork会复制我现有的进程,这样就会使内存使用量翻倍,然后就失败了??
“写时复制”是怎么回事?

我能否在不使用fork的情况下启动一个新进程(或者至少不复制内存 - 从头开始)?

相关内容:

fork()、vfork()、exec()和clone()之间的区别

fork()和内存分配行为

Python subprocess.Popen在一段时间后出现OSError: [Errno 12] Cannot allocate memory的错误

使用subprocess.Popen时的Python内存分配错误

1 个回答

4

看起来没有真正的解决方案会出现(也就是说,没有一个使用vfork的subprocess的替代实现)。那么,我们来个小花招吧?在你的程序开始时,先启动一个小助手进程,它占用的内存很少,随时准备启动你的子进程,并在主进程的整个生命周期内与它保持开放的通信。

这里有个例子,使用rfoo(http://code.google.com/p/rfoo/)和一个叫做rfoosocket的命名unix套接字(当然,你也可以使用rfoo支持的其他连接类型,或者其他RPC库):

服务器:

import rfoo
import subprocess

class MyHandler(rfoo.BaseHandler):
    def RPopen(self, cmd):
        c = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True)
        c.wait()
        return c.stdout.read()

rfoo.UnixServer(MyHandler).start('rfoosocket')

客户端:

import rfoo

# Waste a bunch of memory before spawning the child. Swap out the RPC below
# for a straight popen to show it otherwise fails. Tweak to suit your
# available system memory.
mem = [x for x in range(100000000)]

c = rfoo.UnixConnection().connect('rfoosocket')

print rfoo.Proxy(c).RPopen('ls -l')

如果你需要与启动的子进程进行实时的双向交互,这种模型可能不太适用,但你或许可以想办法实现。你可能需要根据具体需求清理可以传递给Popen的参数,但这应该都比较简单。

你还应该能很容易地在客户端开始时启动服务器,并管理套接字文件(或端口),确保在退出时进行清理。

撰写回答