Python 多处理-共享复杂对象
我有一个类似dict的大型对象,需要在多个工作进程之间共享。每个worker读取对象中的一个随机信息子集,并使用它进行一些计算。我想避免复制大对象,因为我的机器内存很快就用完了 我正在使用的代码,并对其进行了一些修改,以使用固定大小的进程池,这更适合我的用例。然而,这似乎打破了它Python 多处理-共享复杂对象,python,concurrency,multiprocessing,Python,Concurrency,Multiprocessing,我有一个类似dict的大型对象,需要在多个工作进程之间共享。每个worker读取对象中的一个随机信息子集,并使用它进行一些计算。我想避免复制大对象,因为我的机器内存很快就用完了 我正在使用的代码,并对其进行了一些修改,以使用固定大小的进程池,这更适合我的用例。然而,这似乎打破了它 from multiprocessing import Process, Pool from multiprocessing.managers import BaseManager class numeri(obje
from multiprocessing import Process, Pool
from multiprocessing.managers import BaseManager
class numeri(object):
def __init__(self):
self.nl = []
def getLen(self):
return len(self.nl)
def stampa(self):
print self.nl
def appendi(self, x):
self.nl.append(x)
def svuota(self):
for i in range(len(self.nl)):
del self.nl[0]
class numManager(BaseManager):
pass
def produce(listaNumeri):
print 'producing', id(listaNumeri)
return id(listaNumeri)
def main():
numManager.register('numeri', numeri, exposed=['getLen', 'appendi',
'svuota', 'stampa'])
mymanager = numManager()
mymanager.start()
listaNumeri = mymanager.numeri()
print id(listaNumeri)
print '------------ Process'
for i in range(5):
producer = Process(target=produce, args=(listaNumeri,))
producer.start()
producer.join()
print '--------------- Pool'
pool = Pool(processes=1)
for i in range(5):
pool.apply_async(produce, args=(listaNumeri,)).get()
if __name__ == '__main__':
main()
输出是
4315705168
------------过程
生产4315705168
生产4315705168
生产4315705168
生产4315705168
生产4315705168
---------------水池
生产4299771152
生产4315861712
生产4299771152
生产4315861712
生产4299771152
如您所见,在第一种情况下,所有工作进程都获得相同的对象(按id)。在第二种情况下,id不同。这是否意味着正在复制对象
另外,我认为这并不重要,但我使用的是joblib
,它在内部使用了池
:
from joblib import delayed, Parallel
print '------------- Joblib'
Parallel(n_jobs=4)(delayed(produce)(listaNumeri) for i in range(5))
哪些产出:
-----------Joblib
生产4315862096
生产4315862288
生产4315862480
生产4315862672
生产4315862352
如果在代码中添加两行,您会发现这种行为非常奇怪:
def produce(listaNumeri):
print 'producing', id(listaNumeri)
print listaNumeri # <- New line
return id(listaNumeri)
def main():
numManager.register('numeri', numeri, exposed=['getLen', 'appendi', 'svuota', 'stampa', 'getAll'])
mymanager = numManager()
mymanager.start()
listaNumeri = mymanager.numeri()
print listaNumeri # <- New line
print id(listaNumeri)
因此,
numeri
对象每次都被包装在一个AutoProxy
中,而AutoProxy
并不总是相同的。但是,在每次调用product
时,要包装的numeri
对象是相同的。如果在product
中调用appendi
方法一次,则listaNumeri
将在程序的末尾包含10项。您将对象实例numeri
与其管理器listaNumeri
混淆。这可以通过对代码进行一些小的修改来说明:
首先将get\u list\u id
方法添加到class numeri(object)
中,该方法返回所使用的实际内部数据结构的id
:
...
def get_list_id(self): # added method
return id(self.nl)
然后修改product()
以使用它:
def produce(listaNumeri):
print 'producing', id(listaNumeri)
print ' with list_id', listaNumeri.get_list_id() # added
return id(listaNumeri)
最后,确保将新方法作为numManager
接口的一部分公开:
def main():
numManager.register('numeri', numeri, exposed=['getLen', 'appendi',
'svuota', 'stampa',
'get_list_id']) # added
...
之后,您将看到类似以下输出的内容:
<__main__.numeri object at 0x103892990>
4354247888
------------ Process
producing 4354247888
<__main__.numeri object at 0x103892990>
producing 4354247888
<__main__.numeri object at 0x103892990>
producing 4354247888
<__main__.numeri object at 0x103892990>
producing 4354247888
<__main__.numeri object at 0x103892990>
producing 4354247888
<__main__.numeri object at 0x103892990>
--------------- Pool
producing 4352988560
<__main__.numeri object at 0x103892990>
producing 4354547664
<__main__.numeri object at 0x103892990>
producing 4352988560
<__main__.numeri object at 0x103892990>
producing 4354547664
<__main__.numeri object at 0x103892990>
producing 4352988560
<__main__.numeri object at 0x103892990>
13195568
------------过程
生产12739600
列表号为13607080
生产12739600
列表号为13607080
生产12739600
列表号为13607080
生产12739600
列表号为13607080
生产12739600
列表号为13607080
---------------水池
生产13690384
列表号为13607080
生产13691920
列表号为13607080
生产13691888
列表号为13607080
生产13691856
列表号为13607080
生产13691824
列表号为13607080
如图所示,尽管每个
池
进程都有不同的管理器
对象,但它们都在使用(共享)相同的“托管”数据对象。恐怕这里没有任何东西能像您希望的那样工作:-(
首先请注意,相同的id()
不同进程生成的值无法告诉您对象是否真的是相同的对象。每个进程都有自己的虚拟地址空间,由操作系统分配。两个进程中相同的虚拟地址可以引用完全不同的物理内存位置。您的代码是否生成相同的id()
输出与否完全是偶然的。在多次运行中,有时我会在进程
部分看到不同的id()
输出,并在池
部分重复id()
输出,反之亦然,或者两者都重复
其次,Manager
提供语义共享,但不提供物理共享。您的numeri
实例的数据仅存在于Manager进程中。您的所有工作进程都可以查看(的副本)代理对象。这些是转发要由管理器进程执行的所有操作的精简包装。这涉及到大量进程间通信和管理器进程内的序列化。这是编写速度非常慢的代码的一种好方法;-)是的,只有一个numeri
数据的副本,但它的所有工作都是由一个过程(经理过程)完成的
要更清楚地看到这一点,请进行@martineau建议的更改,并将get\u list\u id()
更改为:
def get_list_id(self): # added method
import os
print("get_list_id() running in process", os.getpid())
return id(self.nl)
以下是示例输出:
41543664
------------ Process
producing 42262032
get_list_id() running in process 5856
with list_id 44544608
producing 46268496
get_list_id() running in process 5856
with list_id 44544608
producing 42262032
get_list_id() running in process 5856
with list_id 44544608
producing 44153904
get_list_id() running in process 5856
with list_id 44544608
producing 42262032
get_list_id() running in process 5856
with list_id 44544608
--------------- Pool
producing 41639248
get_list_id() running in process 5856
with list_id 44544608
producing 41777200
get_list_id() running in process 5856
with list_id 44544608
producing 41776816
get_list_id() running in process 5856
with list_id 44544608
producing 41777168
get_list_id() running in process 5856
with list_id 44544608
producing 41777136
get_list_id() running in process 5856
with list_id 44544608
明白了吗?您每次获得相同的列表id的原因是不是,因为每个工作进程都有相同的self.nl
成员,这是因为所有numeri
方法都在一个进程(管理进程)中运行。这就是列表id始终相同的原因
如果您运行的是Linux-y系统(一个支持fork()
)的操作系统),那么更好的办法是在启动任何工作进程之前,忘记所有这些Manager
内容,在模块级创建复杂对象。然后辅助对象将继承复杂对象的(地址空间副本)。通常的写时复制fork()
语义将尽可能提高内存效率。如果不需要将突变折叠回主程序的复杂对象副本中,这就足够了。如果突变确实需要重新折叠,那么您就需要大量的进程间通信,多处理
相应地变得不那么吸引人
这里没有简单的答案。不要向信使开枪;-) 检查,它以一种简单的方式在进程之间共享信息。谢谢,这三个答案都很好,我希望我能接受它们。我理解你的观点,这是一种编写慢代码的好方法,我只是尝试了一下。我的应用程序目前内存有限,因为我没有足够的RAM来运行像内核一样多的工作线程。我希望
41543664
------------ Process
producing 42262032
get_list_id() running in process 5856
with list_id 44544608
producing 46268496
get_list_id() running in process 5856
with list_id 44544608
producing 42262032
get_list_id() running in process 5856
with list_id 44544608
producing 44153904
get_list_id() running in process 5856
with list_id 44544608
producing 42262032
get_list_id() running in process 5856
with list_id 44544608
--------------- Pool
producing 41639248
get_list_id() running in process 5856
with list_id 44544608
producing 41777200
get_list_id() running in process 5856
with list_id 44544608
producing 41776816
get_list_id() running in process 5856
with list_id 44544608
producing 41777168
get_list_id() running in process 5856
with list_id 44544608
producing 41777136
get_list_id() running in process 5856
with list_id 44544608