web-dev-qa-db-ja.com

Pythonマルチプロセッシングのマネージャー辞書

簡単なマルチプロセッシングコードを次に示します。

from multiprocessing import Process, Manager

manager = Manager()
d = manager.dict()

def f():
    d[1].append(4)
    print d

if __name__ == '__main__':
    d[1] = []
    p = Process(target=f)
    p.start()
    p.join()

私が得る出力は:

{1: []}

なぜ{1: [4]}出力として?

24
Bruce

これがあなたが書いたものです:

# from here code executes in main process and all child processes
# every process makes all these imports
from multiprocessing import Process, Manager

# every process creates own 'manager' and 'd'
manager = Manager() 
# BTW, Manager is also child process, and 
# in its initialization it creates new Manager, and new Manager
# creates new and new and new
# Did you checked how many python processes were in your system? - a lot!
d = manager.dict()

def f():
    # 'd' - is that 'd', that is defined in globals in this, current process 
    d[1].append(4)
    print d

if __name__ == '__main__':
# from here code executes ONLY in main process 
    d[1] = []
    p = Process(target=f)
    p.start()
    p.join()

これがあなたが書いておくべきことです:

from multiprocessing import Process, Manager
def f(d):
    d[1] = d[1] + [4]
    print d

if __name__ == '__main__':
    manager = Manager() # create only 1 mgr
    d = manager.dict() # create only 1 dict
    d[1] = []
    p = Process(target=f,args=(d,)) # say to 'f', in which 'd' it should append
    p.start()
    p.join()
31
akaRem

_d[1]_に追加された新しいアイテムが印刷されない理由は、 Pythonの公式ドキュメント に記載されています。

Dictとlistプロキシの変更可能な値または項目への変更は、プロキシを介して伝達されません。これは、プロキシがその値または項目がいつ変更されたかを知る方法がないためです。このようなアイテムを変更するには、変更したオブジェクトをコンテナプロキシに再度割り当てます。

したがって、これは実際に何が起こるかです:

_from multiprocessing import Process, Manager

manager = Manager()
d = manager.dict()

def f():
    # invoke d.__getitem__(), returning a local copy of the empty list assigned by the main process,
    # (consider that a KeyError exception wasn't raised, so a list was definitely returned),
    # and append 4 to it, however this change is not propagated through the manager,
    # as it's performed on an ordinary list with which the manager has no interaction
    d[1].append(4)
    # convert d to string via d.__str__() (see https://docs.python.org/2/reference/datamodel.html#object.__str__),
    # returning the "remote" string representation of the object (see https://docs.python.org/2/library/multiprocessing.html#multiprocessing.managers.SyncManager.list),
    # to which the change above was not propagated
    print d

if __name__ == '__main__':
    # invoke d.__setitem__(), propagating this assignment (mapping 1 to an empty list) through the manager
    d[1] = []
    p = Process(target=f)
    p.start()
    p.join()
_

新しいリストを使用して_d[1]_を再割り当てするか、または同じリストを使用して、リストを更新した後、マネージャーをトリガーして変更を伝達します。

_from multiprocessing import Process, Manager

manager = Manager()
d = manager.dict()

def f():
    # perform the exact same steps, as explained in the comments to the previous code snippet above,
    # but in addition, invoke d.__setitem__() with the changed item in order to propagate the change
    l = d[1]
    l.append(4)
    d[1] = l
    print d

if __name__ == '__main__':
    d[1] = []
    p = Process(target=f)
    p.start()
    p.join()
_

_d[1] += [4]_という行も機能します。


または、 SincePython 3.6 から、 このチェンジセット に従って この問題ネストされたプロキシオブジェクト を使用することもできます。これにより、オブジェクトで実行された変更が含まれるプロキシオブジェクトに自動的に伝播されます。したがって、_d[1] = []_の行をd[1] = manager.list()で置き換えると、問題も修正されます。

_from multiprocessing import Process, Manager

manager = Manager()
d = manager.dict()

def f():
    d[1].append(4)
    # the __str__() method of a dict object invokes __repr__() on each of its items,
    # so explicitly invoking __str__() is required in order to print the actual list items
    print({k: str(v) for k, v in d.items()}

if __name__ == '__main__':
    d[1] = manager.list()
    p = Process(target=f)
    p.start()
    p.join()
_

残念ながら、このバグ修正はPython 2.7に移植されていません(Python 2.7.13以降)。


注(Windowsオペレーティングシステムで実行):

説明されている動作はWindowsオペレーティングシステムにも適用されますが、添付のコードスニペットは、Windowsで実行すると失敗します。 異なるプロセス作成メカニズム 、これはCreateProcess()システムコールではなく fork() AP​​Iに依存しています 、これはサポートされていません。

multiprocessingモジュールを介して新しいプロセスが作成されるたびに、Windowsが新しいPython潜在的に危険な副作用を伴う、メインモジュールをインポートするインタープリタープロセス。この問題を回避するために、次のプログラミングガイドラインをお勧めします 推奨

メインモジュールが新しいPythonインタープリターによって安全にインポートできることを確認します。意図しない副作用(新しいプロセスの開始など)を引き起こすことはありません。

したがって、添付されたコードスニペットをWindowsの下でそのまま実行すると、manager = Manager()行が原因で、無限のプロセスが作成されます。 _Manager.dict_句内にManagerおよび_if __name__ == '__main__'_オブジェクトを作成し、_Manager.dict_オブジェクトをf()への引数として渡すことで、これを簡単に修正できます。 this answer と同じように。

問題の詳細については、 this answer をご覧ください。

13
Yoel

これはマネージャのプロキシ呼び出しのバグだと思います。次のように、共有リストの呼び出しメソッドを回避することができます。

from multiprocessing import Process, Manager

manager = Manager()
d = manager.dict()

def f():
    # get the shared list
    shared_list = d[1]

    shared_list.append(4)

    # forces the shared list to 
    # be serialized back to manager
    d[1] = shared_list

    print d

if __name__ == '__main__':
    d[1] = []
    p = Process(target=f)
    p.start()
    p.join()

    print d
12
Carlo Pires
from multiprocessing import Process, Manager
manager = Manager()
d = manager.dict()
l=manager.list()

def f():
    l.append(4)
    d[1]=l
    print d

if __name__ == '__main__':
    d[1]=[]
    p = Process(target=f)
    p.start()
    p.join()
2
zz yzz