J'ai un objet Python assez complexe que je dois partager entre plusieurs processus. Je lance ces processus en utilisant multiprocessing.Process
. Lorsque je partage un objet avec multiprocessing.Queue
et multiprocessing.Pipe
, les objets sont partagés sans problème. Mais lorsque j'essaie de partager un objet avec d'autres objets de module non multiprocesseur, il me semble que Python crée ces objets. Est-ce vrai?
J'ai essayé d'utiliser multitraitement.Valeur. Mais je ne suis pas sûr de ce que le type devrait être? Ma classe d'objet s'appelle MyClass. Mais lorsque j'essaie multiprocess.Value(MyClass, instance)
, il échoue avec:
TypeError: this type has no size
Une idée de ce qui se passe?
Vous pouvez le faire en utilisant les classes "Manager" de multitraitement de Python et une classe de proxy que vous définissez. D'après les documents Python: http://docs.python.org/library/multiprocessing.html#proxy-objects
Ce que vous voulez faire est de définir une classe de proxy pour votre objet personnalisé, puis de partager l'objet à l'aide d'un "Gestionnaire distant" - consultez les exemples de la même page de document lié pour le "gestionnaire distant" où la documentation montre comment partager. une file d'attente distante. Vous allez faire la même chose, mais votre appel à votre_manager_instance.register () inclura votre classe de proxy personnalisée dans sa liste d'arguments.
De cette manière, vous configurez un serveur pour partager l'objet personnalisé avec un proxy personnalisé. Vos clients ont besoin d’accéder au serveur (voir à nouveau l’excellente documentation sur la configuration d’un accès client/serveur à une file d’attente distante, mais au lieu de partager une file d’attente, vous partagez l’accès à votre classe spécifique).
Après de nombreuses recherches et tests, j'ai découvert que "Manager" effectuait ce travail à un niveau d'objet non-complexe.
Le code ci-dessous montre que l'objet inst
est partagé entre plusieurs processus, ce qui signifie que la propriété var
de inst
est modifiée en dehors lorsque le processus enfant le change.
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
D'accord, le code ci-dessus est suffisant si vous n'avez besoin que de partager objets simples.
Pourquoi pas de complexe? Parce que il peut échouer si votre objet est imbriqué (objet dans objet):
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 :)
Je pense que la principale raison de ce comportement est que Manager
est simplement une construction candybar reposant sur des outils de communication de bas niveau tels que pipe/queue.
Donc, cette approche est pas bien recommandée pour les cas de multitraitement. Il est toujours préférable d’utiliser des outils de bas niveau tels que lock/semaphore/pipe/queue ou des outils de haut niveau tels que Redis queue ou Redis publish/subscribe pour les cas d’utilisation compliqués ma recommandation lol).
voici un paquet python que j'ai fait juste pour ça (partage d'objets complexes entre processus).
git: https://github.com/dRoje/pipe-proxy
L'idée est de créer un proxy pour votre objet et de le transmettre à un processus. Ensuite, vous utilisez le proxy comme si vous aviez une référence à l'objet d'origine. Bien que vous ne puissiez utiliser que des appels de méthode, l'accès aux variables d'objet est effectué à l'aide de setters et de getters.
Disons que nous avons un objet appelé ‘exemple’, la création de proxy et d’écoute de proxy est simple:
from pipeproxy import proxy
example = Example()
exampleProxy, exampleProxyListener = proxy.createProxy(example)
Maintenant, vous envoyez le proxy à un autre processus.
p = Process(target=someMethod, args=(exampleProxy,)) p.start()
Utilisez-le dans l'autre processus comme vous utiliseriez l'objet d'origine (exemple):
def someMethod(exampleProxy):
...
exampleProxy.originalExampleMethod()
...
Mais vous devez l'écouter dans le processus principal:
exampleProxyListener.listen()
Lisez plus et trouvez des exemples ici:
http://matkodjipalo.com/index.php/2017/11/12/proxy-solution-python-multiprocessing/
J'ai essayé d'utiliser BaseManager et d'enregistrer ma classe personnalisée pour la rendre heureuse et résoudre le problème de la classe imbriquée, comme l'avait mentionné Tom ci-dessus.
Je pense que la raison principale n'est pas pertinente pour la classe imbriquée, comme dit, mais le mécanisme de communication que python prend en bas niveau. La raison en est que python utilise un mécanisme de communication similaire à celui des sockets pour synchroniser la modification de la classe personnalisée dans un processus serveur de bas niveau. Je pense que cela encapsule certaines méthodes rpc, le rend juste transparent pour l'utilisateur comme s'il appelait les méthodes locales d'un objet de classe imbriqué.
Ainsi, lorsque vous souhaitez modifier, récupérer vos objets définis par vous-même ou certains objets tiers, vous devez définir certaines interfaces au sein de vos processus pour communiquer avec ces processus plutôt que d'obtenir ou de définir directement des valeurs.
Cependant, lorsque vous utilisez les objets multi-imbriqués dans les objets imbriqués, vous pouvez ignorer les problèmes mentionnés ci-dessus, tout comme ce que vous faites dans votre routine habituelle car vos objets imbriqués dans la classe enregistrée ne sont plus des objets proxy, ne passera plus par la routine de communication de type socket identique et est localisée.
Voici le code pratique que j'ai écrit pour résoudre le problème.
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())
Pour éviter certains maux de tête avec des ressources partagées, vous pouvez essayer de collecter des données nécessitant l’accès à une ressource singleton dans une instruction de retour de la fonction mappée, par exemple. pool.imap_unordered
et ensuite le traiter dans une boucle qui récupère les résultats partiels:
for result in in pool.imap_unordered(process_function, iterable_data):
do_something(result)
Si ce ne sont pas beaucoup de données qui sont renvoyées, il peut ne pas y avoir beaucoup de surcharge en faisant cela.