Python fork:';无法分配内存';如果进程消耗超过50%的可用性。记忆
我在Python中分叉进程时遇到了内存分配问题。我知道这个问题已经在这里的其他一些帖子中讨论过了,但是我在任何一个帖子中都找不到好的解决方案 以下是说明问题的示例脚本:Python fork:';无法分配内存';如果进程消耗超过50%的可用性。记忆,python,memory,fork,popen,allocation,Python,Memory,Fork,Popen,Allocation,我在Python中分叉进程时遇到了内存分配问题。我知道这个问题已经在这里的其他一些帖子中讨论过了,但是我在任何一个帖子中都找不到好的解决方案 以下是说明问题的示例脚本: import os import psutil import subprocess pid = os.getpid() this_proc = psutil.Process(pid) MAX_MEM = int(psutil.virtual_memory().free*1E-9) # in GB def consume_memo
import os
import psutil
import subprocess
pid = os.getpid()
this_proc = psutil.Process(pid)
MAX_MEM = int(psutil.virtual_memory().free*1E-9) # in GB
def consume_memory(size):
""" Size in GB """
memory_consumer = []
while get_mem_usage() < size:
memory_consumer.append(" "*1000000) # Adding ~1MB
return(memory_consumer)
def get_mem_usage():
return(this_proc.memory_info()[0]/2.**30)
def get_free_mem():
return(psutil.virtual_memory().free/2.**30)
if __name__ == "__main__":
for i in range(1, MAX_MEM):
consumer = consume_memory(i)
mem_usage = get_mem_usage()
print("\n## Memory usage %d/%d GB (%2d%%) ##" % (int(mem_usage),
MAX_MEM, int(mem_usage*100/MAX_MEM)))
try:
subprocess.call(['echo', '[OK] Fork worked.'])
except OSError as e:
print("[ERROR] Fork failed. Got OSError.")
print(e)
del consumer
请注意,在运行此测试时,我没有激活交换
解决这个问题似乎有两种选择:
- 使用至少两倍于物理内存大小的交换
- 更改Overmit_内存设置:echo 1>/proc/sys/vm/Overmit_内存
Leonhard您所面临的问题实际上与Python无关,而且仅用Python也无法真正改变。按照注释中的建议提前启动分叉过程(executor)似乎是这个场景中最好、最干净的选择 不管是否使用Python,您正在处理Linux(或类似系统)如何创建新进程。您的父进程首先调用,这将创建一个新的子进程作为其自身的副本。当时它实际上并没有在其他地方复制自己(它使用写时复制),但是,它会检查是否有足够的空间可用,如果没有,则会将
errno
设置为12:ENOMEM
->您看到的OSError
异常
是的,允许虚拟机过度分配内存可以抑制出现此错误。。。如果你在孩子身上执行新的程序(最终也会变小)。它不必立即导致任何故障。但这听起来似乎有可能把问题踢得更远
增加内存(添加交换)。突破了限制,只要运行进程的两倍仍然适合可用内存,fork就可以成功。对于后续的exec,交换甚至不需要被利用
似乎还有一个选择,但看起来。。。肮脏的。还有另一个系统调用,它创建一个新进程,该进程最初与其父进程共享内存,其父进程的执行在该点暂停。这个新创建的子进程只能设置由vWork
返回的变量,它可以\u退出
或exec
。因此,它不会通过任何Python接口公开,如果您尝试(我尝试过)使用ctypes
将它直接加载到Python中,它会出错(我猜想,因为Python仍然会执行vWork
之后提到的三个操作,然后在我可以exec
子对象中的其他操作之前)
也就是说,您可以将整个vWork
和exec
委托给加载的共享对象。作为一个非常粗略的概念证明,我只是这样做了:
#include <errno.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
char run(char * const arg[]) {
pid_t child;
int wstatus;
char ret_val = -1;
child = vfork();
if (child < 0) {
printf("run: Failed to fork: %i\n", errno);
} else if (child == 0) {
printf("arg: %s\n", arg[0]);
execv(arg[0], arg);
_exit(-1);
} else {
child = waitpid(child, &wstatus, 0);
if (WIFEXITED(wstatus))
ret_val = WEXITSTATUS(wstatus);
}
return ret_val;
}
有了它,我仍然可以在3/4的可用内存已满的情况下分叉
理论上,它可以“正确”编写,也可以很好地包装以与Python代码集成,但它似乎是一个额外的选项。我还是会回到执行程序
我只是简单地浏览了
concurrent.futures.process
模块,但是一旦它产生了一个工作进程,它似乎在完成之前不会对它进行破坏,因此滥用现有的ProcessPoolExecutor
可能是一个快速而廉价的选择。我在靠近脚本顶部(主要部分)的位置添加了以下内容:
然后提交子流程。调用:
proc = executor.submit(subprocess.call, ['echo', '[OK] Fork worked.'])
proc.result() # can also collect the return value
您是否看过中建议的解决方案?TLDR:从一开始就启动一个小流程,然后可以根据需要启动其他子流程。是的,我考虑过这一点。然而,这需要在我的代码中进行一些更改。此外,消耗所有内存的大对象应该在工作人员之间共享。所以我希望在分叉工人时可以使用这个对象。我希望有一个更简单的方法来解决这个问题。非常感谢您提供的信息丰富的答案,并为我的迟来回复感到抱歉。我的管道中出现了另一个问题(分析一个包含蛋白质序列的18亿条边图),我不得不在最后几周集中精力解决这个问题。我一有时间就用vfork()测试你的建议。@Leo不用担心。实际上,我仍然建议采用执行人流程。:)也许在底层的廉价人的黑客甚至可以吗?这个vWork()
示例确实有效,但边缘相当粗糙。。。(也就是说,如果使用的话,我认为用它制作一个合适的Python绑定模块是值得的。)仍然希望能够提供有用的提示。如果你发现缺了或不对劲,你可以再打我一次。
import ctypes
import os
import psutil
pid = os.getpid()
this_proc = psutil.Process(pid)
MAX_MEM = int(psutil.virtual_memory().free*1E-9) # in GB
def consume_memory(size):
""" Size in GB """
memory_consumer = []
while get_mem_usage() < size:
memory_consumer.append(" "*1000000) # Adding ~1MB
return(memory_consumer)
def get_mem_usage():
return(this_proc.memory_info()[0]/2.**30)
def get_free_mem():
return(psutil.virtual_memory().free/2.**30)
if __name__ == "__main__":
forker = ctypes.CDLL("forker.so", use_errno=True)
for i in range(1, MAX_MEM):
consumer = consume_memory(i)
mem_usage = get_mem_usage()
print("\n## Memory usage %d/%d GB (%2d%%) ##" % (int(mem_usage),
MAX_MEM, int(mem_usage*100/MAX_MEM)))
try:
cmd = [b"/bin/echo", b"[OK] Fork worked."]
c_cmd = (ctypes.c_char_p * (len(cmd) + 1))()
c_cmd[:] = cmd + [None]
ret = forker.run(c_cmd)
errno = ctypes.get_errno()
if errno:
raise OSError(errno, os.strerror(errno))
except OSError as e:
print("[ERROR] Fork failed. Got OSError.")
print(e)
del consumer
def nop():
pass
executor = concurrent.futures.ProcessPoolExecutor(max_workers=1)
executor.submit(nop) # start a worker process in the pool
proc = executor.submit(subprocess.call, ['echo', '[OK] Fork worked.'])
proc.result() # can also collect the return value