发送到
pool.map(以及相关方法)的所有内容实际上都没有使用共享的写时复制资源。这些值被“
pickled”(Python的序列化机制),通过管道发送到工作进程,然后在其中取消pick,从而从头开始在子进程中重建对象。因此,在这种情况下,每个孩子最终都会得到原始数据的写时复制版本(它从未使用过,因为被告知要使用通过IPC发送的副本),并且个人重新获得了原始数据。在孩子中重建,不共享。
如果要利用分叉的写时复制优势,则不能通过管道发送数据(或引用数据的对象)。您必须将它们存储在可以通过访问其自己的全局变量从孩子那里找到的位置。因此,例如:
import timefrom multiprocessing import Poolimport numpy as npclass MyClass(object): def __init__(self): self.myAttribute = np.zeros(100000000) # basically a big memory struct 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 ** 2def 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 locationmy_instance = MyClass()if __name__ == '__main__': my_instance.my_multithreaded_analysis()
通过不传递
self,可以避免进行复制,而仅使用写时复制映射到子对象的单个全局对象。如果需要多个对象,则可以在创建池之前对对象的实例进行全局
list或
dict映射,然后将可以将对象作为参数的一部分的索引或键传递给
pool.map。然后,worker函数使用索引/键(必须通过IPC对其进行腌制并将其发送给子代)在全局dict(还包括写时复制映射)中查找值(写时复制映射),因此,您可以复制便宜的信息以在孩子中查找昂贵的数据而不进行复制。
如果对象很小,即使您不对其进行写操作,它们也将最终被复制。
CPython被引用计数,并且引用计数出现在公共对象标头中,并且仅通过引用该对象就可以不断更新,即使它是逻辑上不变的引用。因此,将写入并复制小对象(以及分配在同一页内存中的所有其他对象)。对于大型对象(一亿个元素的numpy数组),只要您不对其进行写操作,大部分内容将保持共享状态,因为标头仅占据许多页面之一



