Как работает multiprocessing.Manager () в python? - PullRequest
45 голосов
/ 24 февраля 2012

У меня есть проблема с многопроцессорностью. 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=[])

Меня смутило, почему список не был изменен, как ожидалось?Кто-нибудь может помочь мне выяснить, что случилось?Заранее спасибо!

Ответы [ 2 ]

49 голосов
/ 24 февраля 2012

Прокси-объекты диспетчера не могут распространять изменения, внесенные в (неуправляемые) изменяемые объекты внутри контейнера. Другими словами, если у вас есть объект 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]

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

19 голосов
/ 24 февраля 2012

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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...