1. ホーム
  2. python

multiprocessing.Manager()を使うには?

2023-11-16 10:41:14

質問

私は、以下の点について懸念しています。 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=[])

なぜ私が期待したようにリストが変更されなかったのか、困惑しています。何が起こったのかを理解するために、どなたか助けていただけませんか?

どのように解決するのですか?

マネージャプロキシオブジェクトは、コンテナ内の(管理されていない)Mutableオブジェクトに加えられた変更を伝搬することができません。ですから、言い換えれば、もしあなたが 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]

ご覧の通り、新しい値が管理対象コンテナに直接割り当てられると変化し、管理対象コンテナ内のミュータブルコンテナに割り当てられると変化しません。しかし、ミュータブルコンテナが次に 再割り当て が管理対象コンテナに再割り当てされると、再び変更されます。ネストされた管理対象コンテナを使用することも有効で、親コンテナに割り当て直すことなく直接変更を検出します。