web-dev-qa-db-ja.com

Pythonプロセス間で複雑なオブジェクトを共有しますか?

かなり複雑なPython複数のプロセス間で共有する必要があるオブジェクトです。これらのプロセスを_multiprocessing.Process_を使用して起動します。オブジェクトを_multiprocessing.Queue_および_multiprocessing.Pipe_、それらはうまく共有されていますが、オブジェクトを他の非マルチプロセッシングモジュールオブジェクトと共有しようとすると、Pythonこれらのオブジェクトをフォークします。それは本当ですか?

Multiprocessing.Valueを使用してみました。しかし、私はタイプがどうあるべきか分かりませんか?私のオブジェクトクラスはMyClassと呼ばれます。しかし、multiprocess.Value(MyClass, instance)を試すと、次のように失敗します:

_TypeError: this type has no size_

何が起こっているのでしょうか?

36
Paul

これを行うには、Pythonのマルチプロセッシング「マネージャー」クラスと定義したプロキシクラスを使用します。 Python docs: http://docs.python.org/library/multiprocessing.html#proxy-objects から

あなたがしたいのは、カスタムオブジェクトのプロキシクラスを定義し、「リモートマネージャー」を使用してオブジェクトを共有することです。ドキュメントが共有する方法を示す「リモートマネージャー」の同じリンクされたドキュメントページの例を見てくださいリモートキュー。同じことをすることになりますが、your_manager_instance.register()を呼び出すと、引数リストにカスタムプロキシクラスが含まれます。

この方法では、カスタムオブジェクトをカスタムプロキシと共有するようにサーバーを設定しています。クライアントはサーバーにアクセスする必要があります(ここでも、リモートキューへのクライアント/サーバーアクセスをセットアップする方法の優れたドキュメント例を参照してください。ただし、キューを共有する代わりに、特定のクラスへのアクセスを共有します)。

29
David

多くの調査とテストの結果、「マネージャー」が非複雑オブジェクトレベルでこの仕事をしていることがわかりました。

以下のコードは、オブジェクトinstがプロセス間で共有されていることを示しています。つまり、varのプロパティinstは、子プロセスが変更すると外部で変更されます。

from multiprocessing import Process, Manager
from multiprocessing.managers import BaseManager

class SimpleClass(object):
    def __init__(self):
        self.var = 0

    def set(self, value):
        self.var = value

    def get(self):
        return self.var


def change_obj_value(obj):
    obj.set(100)


if __== '__main__':
    BaseManager.register('SimpleClass', SimpleClass)
    manager = BaseManager()
    manager.start()
    inst = manager.SimpleClass()

    p = Process(target=change_obj_value, args=[inst])
    p.start()
    p.join()

    print inst                    # <__main__.SimpleClass object at 0x10cf82350>
    print inst.get()              # 100

さて、上記のコードはenoughのみ共有する必要がある場合simple objectsです。

なぜ複雑なのですか?なぜなら失敗する可能性があるオブジェクトがネストされている場合(オブジェクト内のオブジェクト):

from multiprocessing import Process, Manager
from multiprocessing.managers import BaseManager

class GetSetter(object):
    def __init__(self):
        self.var = None

    def set(self, value):
        self.var = value

    def get(self):
        return self.var


class ChildClass(GetSetter):
    pass

class ParentClass(GetSetter):
    def __init__(self):
        self.child = ChildClass()
        GetSetter.__init__(self)

    def getChild(self):
        return self.child


def change_obj_value(obj):
    obj.set(100)
    obj.getChild().set(100)


if __== '__main__':
    BaseManager.register('ParentClass', ParentClass)
    manager = BaseManager()
    manager.start()
    inst2 = manager.ParentClass()

    p2 = Process(target=change_obj_value, args=[inst2])
    p2.start()
    p2.join()

    print inst2                    # <__main__.ParentClass object at 0x10cf82350>
    print inst2.getChild()         # <__main__.ChildClass object at 0x10cf6dc50>
    print inst2.get()              # 100
    #good!

    print inst2.getChild().get()   # None
    #bad! you need to register child class too but there's almost no way to do it
    #even if you did register child class, you may get PicklingError :)

この動作の主な理由は、Managerがパイプ/キューのような低レベルのコミュニケーションツールの上に構築されたキャンディバーにすぎないためだと思います。

したがって、この方法はnotがマルチプロセッシングの場合に推奨されます。 lock/semaphore/pipe/queueのような低レベルツール、またはRedisキューまたはRedisパブリッシュ/サブスクライブ)のような高レベルツールを使用できる場合、常に優れています。複雑なユースケース用(私の推奨事項のみですlol)。

29
Tom

python私はそのために作ったパッケージです(プロセス間で複雑なオブジェクトを共有します)。

git: https://github.com/dRoje/pipe-proxy

目的は、オブジェクトのプロキシを作成し、それをプロセスに渡すことです。次に、元のオブジェクトへの参照があるようにプロキシを使用します。メソッド呼び出しのみを使用できますが、オブジェクト変数へのアクセスはセッターとゲッターを使用して行われます。

「example」というオブジェクトがあるとします。プロキシとプロキシリスナーの作成は簡単です。

from pipeproxy import proxy 
example = Example() 
exampleProxy, exampleProxyListener = proxy.createProxy(example) 

次に、プロキシを別のプロセスに送信します。

p = Process(target=someMethod, args=(exampleProxy,)) p.start()

元のオブジェクトを使用する場合と同様に、他のプロセスで使用します(例)。

def someMethod(exampleProxy):
    ...
    exampleProxy.originalExampleMethod()
    ...

ただし、メインプロセスでそれを聞く必要があります。

exampleProxyListener.listen()

詳細を読んで、ここで例を見つけてください:

http://matkodjipalo.com/index.php/2017/11/12/proxy-solution-python-multiprocessing/

5
Duje

BaseManagerを使用して、カスタマイズしたクラスを登録して幸せにしようとしました。また、Tomが前述したように、ネストされたクラスに関する問題を取得しました。

主な理由は、ネストされたクラスとは無関係であると思いますが、python低レベルでの通信メカニズムです。理由はpythonソケットの使用ですサーバープロセス内のカスタマイズされたクラスの変更を低レベルで同期するための同様の通信メカニズム。一部のrpcメソッドをカプセル化し、ネストされたクラスオブジェクトのローカルメソッドを呼び出したかのようにユーザーに対して透過的にする。

そのため、自己定義オブジェクトまたはサードパーティのオブジェクトを変更、取得する場合、値を直接取得または設定するのではなく、プロセス内でインターフェイスを定義して通信する必要があります。

ただし、ネストされたオブジェクトの複数のネストされたオブジェクトを操作する場合、登録されたクラスのネストされたオブジェクトはもはやプロキシオブジェクトではないため、一般的なルーチンで行うように、上記の問題を無視できますソケットに似た通信ルーチンを再び通過せず、ローカライズされます。

この問題を解決するために作成した実行可能なコードを次に示します。

from multiprocessing import Process, Manager, Lock
from multiprocessing.managers import BaseManager
import numpy as np

class NestedObj(object):
       def __init__(self):
                self.val = 1

class CustomObj(object):
        def __init__(self, numpy_obj):
                self.numpy_obj = numpy_obj
                self.nested_obj = NestedObj()

        def set_value(self, p, q, v):
                self.numpy_obj[p, q] = v

        def get_obj(self):
                return self.numpy_obj

        def get_nested_obj(self):
                return self.nested_obj.val

class CustomProcess(Process):
        def __init__(self, obj, p, q, v):
                super(CustomProcess, self).__init__()
                self.obj = obj
                self.index = p, q
                self.v = v

        def run(self):
                self.obj.set_value(*self.index, self.v)



if __name__=="__main__":
        BaseManager.register('CustomObj', CustomObj)
        manager = BaseManager()
        manager.start()
        data = [[0 for x in range(10)] for y in range(10)]
        matrix = np.matrix(data)
        custom_obj = manager.CustomObj(matrix)
        print(custom_obj.get_obj())
        process_list = []
        for p in range(10):
                for q in range(10):
                        proc = CustomProcess(custom_obj, p, q, 10*p+q)
                        process_list.append(proc)
        for x in range(100):
                process_list[x].start()
        for x in range(100):
                process_list[x].join()
        print(custom_obj.get_obj())
        print(custom_obj.get_nested_obj())
2
Zhong

共有リソースで頭痛を軽減するには、シングルトンリソースへのアクセスが必要なデータを、たとえば、 pool.imap_unorderedそして、部分的な結果を取得するループでさらに処理します。

for result in in pool.imap_unordered(process_function, iterable_data):
    do_something(result)

返されるデータがそれほど多くない場合、これを行う際のオーバーヘッドはあまりないかもしれません。

0
Lenar Hoyt

Python 3.6では、ドキュメントは次のように述べています。

バージョン3.6で変更:共有オブジェクトはネスト可能です。たとえば、共有リストなどの共有コンテナオブジェクトには、SyncManagerによってすべて管理および同期される他の共有オブジェクトを含めることができます。

SyncManagerを介してインスタンスが作成される限り、オブジェクトが相互に参照できるようにする必要があります。ただし、別のタイプのオブジェクトのメソッドで1つのタイプのオブジェクトを動的に作成することは、依然として不可能であるか、非常に難しい場合があります。

編集:私はこの問題につまずいた マルチプロセッシングマネージャーとカスタムクラス with python 3.6.5および3.6.7。チェックする必要があるpython = 3.7

編集2:他のいくつかの問題のため、私は現在これをpython3.7でテストすることはできません。 https://stackoverflow.com/a/50878600/7541006 で提供されている回避策は私にとってはうまくいきます

0