it-swarm.com.de

Ein komplexes Objekt zwischen Python-Prozessen gemeinsam nutzen?

Ich habe ein ziemlich komplexes Python-Objekt, das ich zwischen mehreren Prozessen teilen muss. Ich starte diese Prozesse mit multiprocessing.Process. Wenn ich ein Objekt mit multiprocessing.Queue und multiprocessing.Pipe darin teile, werden sie einfach geteilt. Wenn ich jedoch versuche, ein Objekt mit anderen Nicht-Multiprocessing-Modul-Objekten zu teilen, scheint es, als würde Python diese Objekte forcieren. Ist das wahr?

Ich habe versucht, Multiprocessing.Value zu verwenden. Ich bin mir aber nicht sicher, was der Typ sein soll? Meine Objektklasse heißt MyClass. Aber wenn ich multiprocess.Value(MyClass, instance) versuche, schlägt es fehl mit:

TypeError: this type has no size

Irgendeine Idee, was los ist

31
Paul

Sie können dies mit den Multiprocessing "Manager" -Klassen von Python und einer von Ihnen definierten Proxy-Klasse tun. Aus den Python-Dokumenten: http://docs.python.org/library/multiprocessing.html#proxy-objects

Sie möchten eine Proxy-Klasse für Ihr benutzerdefiniertes Objekt definieren und das Objekt anschließend mit einem "Remote Manager" freigeben. Sehen Sie sich die Beispiele auf derselben verknüpften Dokumentseite für "Remote Manager" an, in denen die Dokumente zeigen, wie freigegeben werden soll eine entfernte Warteschlange. Sie werden dasselbe tun, aber Ihr Aufruf von your_manager_instance.register () wird Ihre benutzerdefinierte Proxy-Klasse in die Argumentliste aufnehmen. 

Auf diese Weise richten Sie einen Server ein, um das benutzerdefinierte Objekt für einen benutzerdefinierten Proxy freizugeben. Ihre Clients benötigen Zugriff auf den Server (siehe auch die hervorragenden Dokumentationsbeispiele für das Einrichten des Client/Server-Zugriffs auf eine Remote-Warteschlange. Anstatt jedoch eine Warteschlange gemeinsam zu nutzen, haben Sie Zugriff auf Ihre spezifische Klasse).

25
David

Nach langem Suchen und Testen habe ich gefunden, dass "Manager" diese Aufgabe auf einer nicht komplexen Objektebene erledigt.

Der folgende Code zeigt, dass das Objekt inst von Prozessen gemeinsam genutzt wird. Dies bedeutet, dass die Eigenschaft var von inst außerhalb geändert wird, wenn der untergeordnete Prozess es ändert.

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

Okay, der obige Code ist genug wenn Sie nur einfache Objekte teilen müssen .

Warum kein Komplex? Weil es kann fehlschlagen wenn Ihr Objekt verschachtelt ist (Objekt im Objekt):

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 :)

Ich denke, der Hauptgrund für dieses Verhalten ist, dass Manager nur eine Süßigkeitenleiste ist, die auf Low-Level-Kommunikationswerkzeugen wie Pipe/Queue aufgebaut ist.

Daher ist dieser Ansatz für einen Multiprocessing-Fall nicht empfehlenswert. Es ist immer besser, wenn Sie Low-Level-Tools wie lock/Semaphore/Pipe/Queue oder High-Level-Tools wie Redis Queue oder Redis Publish/Subscribe für komplizierte Anwendungsfälle (nur meine Empfehlung lol).

21
Tom

hier ist ein Python-Paket, das ich eigens dafür erstellt habe (komplexe Objekte zwischen Prozessen teilen).

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

Die Idee ist, dass Sie einen Proxy für Ihr Objekt erstellen und ihn an einen Prozess übergeben. Dann verwenden Sie den Proxy, als hätten Sie einen Verweis auf das Originalobjekt. Obwohl Sie nur Methodenaufrufe verwenden können, erfolgt der Zugriff auf Objektvariablen mit Setzern und Gettern. 

Nehmen wir an, wir haben ein Objekt namens 'example', das Erstellen von Proxy- und Proxy-Listener ist einfach:

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

Jetzt senden Sie den Proxy an einen anderen Prozess.

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

Verwenden Sie es im anderen Prozess so, wie Sie das ursprüngliche Objekt verwenden würden (Beispiel):

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

Aber Sie müssen es im Hauptprozess hören:

exampleProxyListener.listen()

Lesen Sie mehr und finden Sie Beispiele hier:

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

4
Duje

Ich habe versucht, BaseManager zu verwenden und meine angepasste Klasse zu registrieren, um sie glücklich zu machen, und das Problem mit der verschachtelten Klasse wird genau wie Tom oben erwähnt.

Ich denke, dass der Hauptgrund für die geschachtelte Klasse irrelevant ist, wie gesagt, aber der Kommunikationsmechanismus, den Python auf niedrigem Niveau einnimmt. Der Grund ist, dass Python einen socketähnlichen Kommunikationsmechanismus verwendet, um die Änderung der benutzerdefinierten Klasse in einem Serverprozess auf niedriger Ebene zu synchronisieren. Ich denke, es kapselt einige rpc-Methoden, macht es für den Benutzer einfach transparent, als würden sie die lokalen Methoden eines verschachtelten Klassenobjekts aufrufen.

Wenn Sie also selbstdefinierte Objekte oder Objekte von Drittanbietern ändern, abrufen möchten, sollten Sie einige Schnittstellen in Ihren Prozessen definieren, um mit ihm zu kommunizieren, anstatt direkt Werte abzurufen oder festzulegen.

Wenn Sie die mehrfach geschachtelten Objekte in den verschachtelten Objekten verwenden, können Sie die oben genannten Probleme ebenso ignorieren, wie Sie es in Ihrer Routine tun, da Ihre verschachtelten Objekte in der registrierten Klasse keine Proxy-Objekte mehr sind, auf denen die Operation ausgeführt wird wird die sockelähnliche Kommunikationsroutine nicht erneut durchlaufen und ist lokalisiert.

Hier ist der ausführbare Code, den ich geschrieben habe, um das Problem zu lösen.

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())
1
Zhong

Um einige Kopfschmerzen mit gemeinsam genutzten Ressourcen zu vermeiden, können Sie versuchen, Daten zu sammeln, die Zugriff auf eine Singleton-Ressource in einer return-Anweisung der Funktion benötigen, die z. pool.imap_unordered und dann in einer Schleife weiterverarbeiten, die die Teilergebnisse abruft:

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

Wenn nicht viele Daten zurückgegeben werden, ist dies möglicherweise nicht viel Aufwand.

0
Lenar Hoyt

In Python 3.6 heißt es in den Dokumenten:

In Version 3.6 geändert: Gemeinsam genutzte Objekte können verschachtelt werden. Beispielsweise kann ein freigegebenes Containerobjekt wie eine freigegebene Liste andere freigegebene Objekte enthalten, die alle vom SyncManager verwaltet und synchronisiert werden.

Solange Instanzen über den SyncManager erstellt werden, sollten Sie in der Lage sein, die Objekte aufeinander zu verweisen. Die dynamische Erstellung eines Objekttyps in den Methoden eines anderen Objekttyps ist möglicherweise immer noch unmöglich oder sehr schwierig.

Edit: Ich bin auf dieses Problem gestoßen Multiprocessing-Manager und benutzerdefinierte Klassen mit python 3.6.5 und 3.6.7. Muss python = 3,7

Edit 2: Aufgrund einiger anderer Probleme kann ich dies derzeit nicht mit python3.7 testen. Die in https://stackoverflow.com/a/50878600/7541006 bereitgestellte Problemumgehung funktioniert einwandfrei für mich

0