为什么如果我不操作,多进程会复制我的数据?

6
我正在追踪内存不足的错误,惊讶地发现即使我没有使用大型数组,Python的多进程似乎也会复制它们。我在Linux上使用时,为什么会这样呢?我以为写时复制机制能保护我免受额外的复制。我猜想每当我引用对象时都会触发某种陷阱,然后才进行复制。
对于像30GB自定义字典这样任意数据类型的问题,解决这个问题的正确方法是使用一个Monitor吗?有没有办法构建Python,使它不会产生这种无意义的复制?
import numpy as np
import psutil
from multiprocessing import Process
mem=psutil.virtual_memory()
large_amount=int(0.75*mem.available)

def florp():
    print("florp")

def bigdata():
    return np.ones(large_amount,dtype=np.int8)

if __name__=='__main__':
    foo=bigdata()#Allocated 0.75 of the ram, no problems
    p=Process(target=florp)
    p.start()#Out of memory because bigdata is copied? 
    print("Wow")
    p.join()

运行中:

[ebuild   R    ] dev-lang/python-3.4.1:3.4::gentoo  USE="gdbm ipv6 ncurses readline ssl threads xml -build -examples -hardened -sqlite -tk -wininst" 0 KiB

Python(或者说CPython)使用嵌入在对象中的引用计数器。每当一个对象被传递给函数时,它的引用计数器就会增加,导致对对象的修改,从而在子进程中产生页面错误。虽然我不会说它能解释你上面提到的特定例子,但仍然建议考虑使用多线程而不是多进程。 - Ulrich Eckhardt
@UlrichEckhardt 但是我没有向 florp 传递任何东西! - Mikhail
你说得对,我输入回车太早了,请看我追加的最后两句话。顺便说一下:我在Linux/x86_64上使用Python 3.4.2时无法重现这些问题。 - Ulrich Eckhardt
你尝试过在C语言中做同样的事情吗:malloc()大量内存,调用fork()并查看发生了什么?尝试不同的启动方法:'forkserver'或'spawn'。相关:如何避免使用子进程模块导致的[Errno 12]无法分配内存错误。查看这个代码示例这个答案 - jfs
2个回答

2

当你将代码传递给Python进行编译时,没有被函数或对象保护的任何内容都会立即被exec执行以进行评估。

在你的情况下,bigdata=np.ones(large_amount,dtype=np.int8)必须被评估--除非你的实际代码有不同的行为,否则未调用florp()与此无关。

以下是一个直接的例子:

>>> f = 0/0
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ZeroDivisionError: integer division or modulo by zero
>>> def f():
...     return 0/0
...
>>>

要将此应用于您的代码,请在函数后面放置bigdata=np.ones(large_amount,dtype=np.int8),并根据需要调用它,否则,Python会尝试通过在运行时使该变量可用来提供帮助。
如果bigdata不改变,您可以编写一个函数,在您保留整个过程期间使用的对象上获取或设置它。
编辑:咖啡刚开始起作用。当您创建一个新进程时,Python将需要将所有对象复制到该新进程中以进行访问。您可以通过使用线程或允许您在进程之间共享内存的机制(例如共享内存映射或共享ctypes)来避免这种情况。

我尝试将数组包装在函数调用中,但没有成功,或者你有其他想法吗?请参见编辑。 - Mikhail
我需要看看你实际在做什么。 我假设 florp() 在你的实际代码中做了更多的事情而不仅仅是打印。哦,实际上,我刚想到一个关于进程的重要区别,请给我一点时间将其添加到我的答案中。 - user559633

1

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