Подтвердить что ты не робот

Как работает multiprocessing.Manager() в python?

У меня есть проблема с multiprocessing.Manager() в python, вот пример,

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=[])

Это смутило меня, почему список не изменился, как и ожидалось? кто-нибудь может помочь мне разобраться в том, что произошло? Спасибо заранее!

4b9b3361

Ответ 1

Прокси-объекты менеджера не могут распространять изменения, внесенные в (неуправляемые) изменяемые объекты внутри контейнера. Другими словами, если у вас есть объект manager.list(), любые изменения в управляемом списке распространяются на все остальные процессы. Но если у вас есть обычный список Python внутри этого списка, любые изменения во внутреннем списке не распространяются, потому что менеджер не может обнаружить изменения.

Чтобы распространять изменения, вам нужно также использовать объекты manager.list() для вложенных списков (требуется Python 3.6 или новее), или вам нужно напрямую изменить объект manager.list() (см. Примечание к manager.list в Python 3.5 или старше).

Например, рассмотрим следующий код и его вывод:

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]

Как вы можете видеть, когда новое значение присваивается непосредственно управляемому контейнеру, оно изменяется; когда он присваивается изменяемому контейнеру в управляемом контейнере, он не изменяется; но если измененный контейнер затем переназначается в управляемый контейнер, он снова изменяется. Использование вложенного управляемого контейнера также работает, обнаруживая изменения непосредственно, не назначая обратно родительскому контейнеру.

Ответ 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