任何发送至pool.map
(和相关方法)实际上并未使用共享的写时复制资源。值为“pickled”(Python的序列化机制) https://docs.python.org/3/library/pickle.html,通过管道发送到工作进程并在那里进行 unpickle,这会从头开始重建子进程中的对象。因此,在这种情况下,每个子进程最终都会得到原始数据的写时复制版本(它从不使用它,因为它被告知要使用通过 IPC 发送的副本),以及原始数据的个人重新创建。在孩子身上重建并且不被共享。
如果您想利用分叉的写时复制优势,则无法通过管道发送数据(或引用数据的对象)。您必须将它们存储在子级可以通过访问它们自己的全局变量找到的位置。例如:
import os
import time
from multiprocessing import Pool
import numpy as np
class MyClass(object):
def __init__(self):
self.myAttribute = os.urandom(1024*1024*1024) # basically a big memory struct(~1GB size)
def my_multithreaded_analysis(self):
arg_lists = list(range(10)) # Don't pass self
pool = Pool(processes=10)
result = pool.map(call_method, arg_lists)
print result
def analyze(self, i):
time.sleep(10)
return i ** 2
def call_method(i):
# Implicitly use global copy of my_instance, not one passed as an argument
return my_instance.analyze(i)
# Constructed globally and unconditionally, so the instance exists
# prior to forking in commonly accessible location
my_instance = MyClass()
if __name__ == '__main__':
my_instance.my_multithreaded_analysis()
通过不通过self
,您可以避免进行复制,而只需使用写入时复制映射到子对象中的单个全局对象。如果您需要多个对象,您可以创建一个全局对象list
or dict
在创建池之前映射到对象的实例,然后将可以查找对象的索引或键作为参数的一部分传递给pool.map
。然后,工作函数使用索引/键(必须进行腌制并通过 IPC 发送给子进程)在全局字典(也是写入时复制映射)中查找值(写入时复制映射),因此,您可以复制廉价信息来查找子级中昂贵的数据,而无需复制它。
如果对象很小,即使您不写入它们,它们最终也会被复制。 CPython 是引用计数的,引用计数出现在公共对象头中,并且仅通过引用对象就不断更新,即使它是逻辑上不可变的引用。因此,小对象(以及在同一内存页中分配的所有其他对象)将被写入,并因此被复制。对于大型对象(一亿个元素的 numpy 数组),只要您不对其进行写入,其中大部分都会保持共享状态,因为标头仅占用许多页面中的一页
python 版本 3.8 中的更改:在 macOS 上,spawn start 方法现在是默认方法。看多处理文档 https://docs.python.org/3/library/multiprocessing.html。 Spawn 没有利用写时复制。