【问题标题】:How to use a multiprocessing.Manager()?如何使用 multiprocessing.Manager()?
【发布时间】:2012-03-15 06:11:09
【问题描述】:

我担心 python 中的multiprocessing.Manager()。示例如下:

import multiprocessing

def f(ns):
    ns.x *=10
    ns.y *= 10

if __name__ == '__main__':
    manager = multiprocessing.Manager()
    ns = manager.Namespace()
    ns.x = 1
    ns.y = 2

    print 'before', ns
    p = multiprocessing.Process(target=f, args=(ns,))
    p.start()
    p.join()
    print 'after', ns

输出是:

before Namespace(x=1, y=2)
after Namespace(x=10, y=20)

到目前为止,它按我的预期工作,然后我修改了这样的代码:

import multiprocessing

def f(ns):
    ns.x.append(10)
    ns.y.append(10)

if __name__ == '__main__':
    manager = multiprocessing.Manager()
    ns = manager.Namespace()
    ns.x = []
    ns.y = []

    print 'before', ns
    p = multiprocessing.Process(target=f, args=(ns,))
    p.start()
    p.join()
    print 'after', ns

现在的输出是:

before Namespace(x=[], y=[])
after Namespace(x=[], y=[])

这让我很困惑,为什么列表没有按我的预期进行更改。谁能帮我弄清楚发生了什么?

【问题讨论】:

    标签: python multiprocessing python-2.x


    【解决方案1】:

    Manager 代理对象无法传播对容器内(非托管)可变对象所做的更改。所以换句话说,如果你有一个manager.list() 对象,对托管列表本身的任何更改都会传播到所有其他进程。但是如果你有一个普通的 Python 列表inside,那么对内部列表的任何更改都不会传播,因为管理器无法检测到更改。

    为了传播更改,您也必须对嵌套列表使用manager.list() 对象(需要Python 3.6 or newer),或者您需要直接修改manager.list() 对象(参见注释on manager.list in Python 3.5 or older)。

    例如,考虑以下代码及其输出:

    import multiprocessing
    import time
    
    def f(ns, ls, di):
        ns.x += 1
        ns.y[0] += 1
        ns_z = ns.z
        ns_z[0] += 1
        ns.z = ns_z
    
        ls[0] += 1
        ls[1][0] += 1 # unmanaged, not assigned back
        ls_2 = ls[2]  # unmanaged...
        ls_2[0] += 1
        ls[2] = ls_2  # ... but assigned back
        ls[3][0] += 1 # managed, direct manipulation
    
        di[0] += 1
        di[1][0] += 1 # unmanaged, not assigned back
        di_2 = di[2]  # unmanaged...
        di_2[0] += 1
        di[2] = di_2  # ... but assigned back
        di[3][0] += 1 # managed, direct manipulation
    
    if __name__ == '__main__':
        manager = multiprocessing.Manager()
        ns = manager.Namespace()
        ns.x = 1
        ns.y = [1]
        ns.z = [1]
        ls = manager.list([1, [1], [1], manager.list([1])])
        di = manager.dict({0: 1, 1: [1], 2: [1], 3: manager.list([1])})
    
        print('before', ns, ls, ls[2], di, di[2], sep='\n')
        p = multiprocessing.Process(target=f, args=(ns, ls, di))
        p.start()
        p.join()
        print('after', ns, ls, ls[2], di, di[2], sep='\n')
    

    输出:

    before
    Namespace(x=1, y=[1], z=[1])
    [1, [1], [1], <ListProxy object, typeid 'list' at 0x10b8c4630>]
    [1]
    {0: 1, 1: [1], 2: [1], 3: <ListProxy object, typeid 'list' at 0x10b8c4978>}
    [1]
    after
    Namespace(x=2, y=[1], z=[2])
    [2, [1], [2], <ListProxy object, typeid 'list' at 0x10b8c4630>]
    [2]
    {0: 2, 1: [1], 2: [2], 3: <ListProxy object, typeid 'list' at 0x10b8c4978>}
    [2]
    

    如您所见,当一个新值直接分配给托管容器时,它会发生变化;当它被分配给托管容器中的可变容器时,它不会改变;但如果可变容器随后重新分配到托管容器,它会再次更改。使用嵌套的托管容器也可以,直接检测更改而无需分配回父容器。

    【讨论】:

    • 从 3.6 开始,对嵌套对象的更改会自动传播。
    • 我在 Python 3.6.4 上使用管理器在名称空间中使用嵌套字典时遇到了一些问题。确保您的嵌套对象正确更新,然后再假设它们是正确的。对我来说,解决方案是将每个要共享的对象显式定义为 Manager 对象。
    • @max:前提是那些嵌套对象是也是代理对象。此答案中的代码嵌套了常规的非代理列表。 lsdi 中的嵌套列表需要包装在 manager.list() 调用中。
    • @MartijnPieters,感谢您的澄清!我知道我根本不明白那个评论。
    【解决方案2】:

    ns 是一个 NamespaceProxy 实例。这些对象具有特殊的__getattr____setattr____delattr__ 方法,允许跨进程共享值。 为了在更改值时利用此机制,您必须触发__setattr__

    ns.x.append(10)
    

    导致调用ns.__getattr__ 以检索ns.x,但不会导致调用ns.__setattr__

    要解决此问题,您必须使用ns.x = ...

    def f(ns):   
        tmp = ns.x     # retrieve the shared value
        tmp.append(10)
        ns.x = tmp     # set the shared value
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-12-22
      • 2017-09-12
      • 1970-01-01
      • 1970-01-01
      • 2016-08-24
      • 2015-07-23
      • 1970-01-01
      • 2021-08-29
      相关资源
      最近更新 更多