python - 多处理 - 共享一个复杂的对象

标签 python concurrency multiprocessing

我有一个大型的 dict 类对象,需要在多个工作进程之间共享。每个工作人员读取对象中信息的随机子集,并用它进行一些计算。我想避免复制大对象,因为我的机器很快就会耗尽内存。

我在玩 this SO question 的代码我对其进行了一些修改以使用固定大小的进程池,这更适合我的用例。然而,这似乎打破了它。

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
------------ Process
producing 4315705168
producing 4315705168
producing 4315705168
producing 4315705168
producing 4315705168
--------------- Pool
producing 4299771152
producing 4315861712
producing 4299771152
producing 4315861712
producing 4299771152

如您所见,在第一种情况下,所有工作进程都获得相同的对象(通过 id)。在第二种情况下,id 不一样。这是否意味着正在复制对象?

附言我认为这不重要,但我使用的是 joblib,它在内部使用了一个 Pool:

from joblib import delayed, Parallel

print '------------- Joblib'
        Parallel(n_jobs=4)(delayed(produce)(listaNumeri) for i in range(5))

哪些输出:

------------- Joblib
producing 4315862096
producing 4315862288
producing 4315862480
producing 4315862672
producing 4315862352

最佳答案

恐怕这里几乎没有任何东西能像您希望那样工作:-(

首先请注意,不同进程产生的相同 id() 值告诉您什么对象是否真的是同一个对象。每个进程都有自己的虚拟地址空间,由操作系统分配。两个进程中的相同虚拟地址可以引用完全不同的物理 内存位置。您的代码是否产生相同的 id() 输出完全是偶然的。在多次运行中,有时我会在您的 Process 部分看到不同的 id() 输出,并在您的 Pool< 中看到重复的 id() 输出 部分,反之亦然,或两者兼而有之。

其次,Manager 提供语义 共享但不提供物理 共享。 numeri 实例的数据存在于管理器进程中。您的所有工作进程都可以看到代理对象(的副本)。这些是瘦包装器,用于转发所有要由管理器进程执行的操作。这涉及大量的进程间通信,以及管理器进程内部的序列化。这是编写非常慢的代码的好方法 ;-) 是的,numeri 数据只有一个副本,但对它的所有工作都是由单个进程(管理器进程)完成的。

为了更清楚地看到这一点,进行@martineau 建议的更改,并将 get_list_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() 语义将使内存效率尽可能高。如果不需要将突变折叠回主程序的复杂对象副本,这就足够了。如果突变确实需要折回,那么您又需要大量的进程间通信,并且 multiprocessing 相应地变得不那么吸引人了。

这里没有简单的答案。不要射杀信使 ;-)

关于python - 多处理 - 共享一个复杂的对象,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20955683/

相关文章:

python - 在 Python 中将维基百科表格抓取为 CSV

java - Swing - 更新标签

python - 具有多处理工作人员的扭曲网络客户端?

java - Java支持的进程和底层操作系统支持的进程意思一样吗?

c - 如果使用共享内存,进程是否仍然比线程有优势?

python - 使用类 ORB 的计算方法生成的描述符数组表示什么?

python - 如何捕获上下文中未定义名称的异常?

python - 使用 Python 的子进程模块包装 Java 程序的问题

java - Runnable 的实现是否应该允许在同一个实例上并发调用 run() ?

java - 非最终字段的安全初始化