web-dev-qa-db-fra.com

Impossible de décaper <type 'instancemethodhod'> lors de l'utilisation du multitraitement Pool.map ()

J'essaie d'utiliser la fonction Pool.map() de multiprocessing pour diviser le travail simultanément. Lorsque j'utilise le code suivant, cela fonctionne bien:

import multiprocessing

def f(x):
    return x*x

def go():
    pool = multiprocessing.Pool(processes=4)        
    print pool.map(f, range(10))


if __name__== '__main__' :
    go()

Cependant, lorsque je l'utilise dans une approche plus orientée objet, cela ne fonctionne pas. Le message d'erreur que cela donne est:

PicklingError: Can't pickle <type 'instancemethod'>: attribute lookup
__builtin__.instancemethod failed

Cela se produit lorsque mon programme principal est le suivant:

import someClass

if __name__== '__main__' :
    sc = someClass.someClass()
    sc.go()

et ce qui suit est ma classe someClass:

import multiprocessing

class someClass(object):
    def __init__(self):
        pass

    def f(self, x):
        return x*x

    def go(self):
        pool = multiprocessing.Pool(processes=4)       
        print pool.map(self.f, range(10))

Quelqu'un sait ce que le problème pourrait être, ou un moyen facile de le contourner?

205
ventolin

Le problème est que le multitraitement doit décaper les choses pour les relier entre les processus, et les méthodes liées ne sont pas décapables. La solution de contournement (que vous considériez cela comme "facile" ou non ;-) est d'ajouter l'infrastructure à votre programme pour permettre le pickling de telles méthodes, en l'enregistrant avec la méthode de bibliothèque standard copy_reg .

Par exemple, la contribution de Steven Bethard à ce fil (vers la fin du fil) montre une approche parfaitement exploitable pour permettre le pickling/le picking de méthode via copy_reg.

115
Alex Martelli

Toutes ces solutions sont laides, car le multitraitement et le décapage sont interrompus et limités à moins que vous ne sautiez en dehors de la bibliothèque standard.

Si vous utilisez un fork de multiprocessing appelé pathos.multiprocesssing, vous pouvez directement utiliser des classes et des méthodes de classe dans les fonctions map du multitraitement. Ceci est dû au fait que dill est utilisé à la place de pickle ou cPickle, et dill peut sérialiser presque tout en python.

pathos.multiprocessing fournit également une fonction de carte asynchrone… et il peut map fonctions avec plusieurs arguments (par exemple, map(math.pow, [1,2,3], [4,5,6])).

Voir: Que peuvent faire le multitraitement et l'aneth ensemble?

et: http://matthewrocklin.com/blog/work/2013/12/05/Parallelism-and-Serialization/

>>> import pathos.pools as pp
>>> p = pp.ProcessPool(4)
>>> 
>>> def add(x,y):
...   return x+y
... 
>>> x = [0,1,2,3]
>>> y = [4,5,6,7]
>>> 
>>> p.map(add, x, y)
[4, 6, 8, 10]
>>> 
>>> class Test(object):
...   def plus(self, x, y): 
...     return x+y
... 
>>> t = Test()
>>> 
>>> p.map(Test.plus, [t]*4, x, y)
[4, 6, 8, 10]
>>> 
>>> p.map(t.plus, x, y)
[4, 6, 8, 10]

Et juste pour être explicite, vous pouvez faire exactement ce que vous voulez faire, et vous pouvez le faire à partir de l'interprète, si vous le souhaitez.

>>> import pathos.pools as pp
>>> class someClass(object):
...   def __init__(self):
...     pass
...   def f(self, x):
...     return x*x
...   def go(self):
...     pool = pp.ProcessPool(4)
...     print pool.map(self.f, range(10))
... 
>>> sc = someClass()
>>> sc.go()
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
>>> 

Obtenez le code ici: https://github.com/uqfoundation/pathos

70
Mike McKerns

Vous pouvez également définir une méthode __call__() à l'intérieur de votre someClass(), qui appelle someClass.go(), puis transmet une instance de someClass() au pool. Cet objet est décapable et cela fonctionne bien (pour moi) ...

33
dorvak

Quelques limites cependant à la solution de Steven Bethard:

Lorsque vous enregistrez votre méthode de classe en tant que fonction, le destructeur de votre classe est appelé de manière surprenante chaque fois que le traitement de votre méthode est terminé. Donc, si vous avez 1 instance de votre classe qui appelle n fois sa méthode, les membres peuvent disparaître entre 2 exécutions et vous pouvez recevoir un message malloc: *** error for object 0x...: pointer being freed was not allocated (par exemple, un fichier de membre ouvert) ou pure virtual method called, terminate called without an active exception (ce qui signifie que la durée de vie d'un objet membre que j'ai utilisé était plus courte que ce que je pensais). Je l'ai eu lorsqu'il s'agit de n supérieur à la taille de la piscine. Voici un court exemple:

from multiprocessing import Pool, cpu_count
from multiprocessing.pool import ApplyResult

# --------- see Stenven's solution above -------------
from copy_reg import pickle
from types import MethodType

def _pickle_method(method):
    func_name = method.im_func.__name__
    obj = method.im_self
    cls = method.im_class
    return _unpickle_method, (func_name, obj, cls)

def _unpickle_method(func_name, obj, cls):
    for cls in cls.mro():
        try:
            func = cls.__dict__[func_name]
        except KeyError:
            pass
        else:
            break
    return func.__get__(obj, cls)


class Myclass(object):

    def __init__(self, nobj, workers=cpu_count()):

        print "Constructor ..."
        # multi-processing
        pool = Pool(processes=workers)
        async_results = [ pool.apply_async(self.process_obj, (i,)) for i in range(nobj) ]
        pool.close()
        # waiting for all results
        map(ApplyResult.wait, async_results)
        lst_results=[r.get() for r in async_results]
        print lst_results

    def __del__(self):
        print "... Destructor"

    def process_obj(self, index):
        print "object %d" % index
        return "results"

pickle(MethodType, _pickle_method, _unpickle_method)
Myclass(nobj=8, workers=3)
# problem !!! the destructor is called nobj times (instead of once)

Sortie:

Constructor ...
object 0
object 1
object 2
... Destructor
object 3
... Destructor
object 4
... Destructor
object 5
... Destructor
object 6
... Destructor
object 7
... Destructor
... Destructor
... Destructor
['results', 'results', 'results', 'results', 'results', 'results', 'results', 'results']
... Destructor

La méthode __call__ n'est pas aussi équivalente car [None, ...] sont lus à partir des résultats:

from multiprocessing import Pool, cpu_count
from multiprocessing.pool import ApplyResult

class Myclass(object):

    def __init__(self, nobj, workers=cpu_count()):

        print "Constructor ..."
        # multiprocessing
        pool = Pool(processes=workers)
        async_results = [ pool.apply_async(self, (i,)) for i in range(nobj) ]
        pool.close()
        # waiting for all results
        map(ApplyResult.wait, async_results)
        lst_results=[r.get() for r in async_results]
        print lst_results

    def __call__(self, i):
        self.process_obj(i)

    def __del__(self):
        print "... Destructor"

    def process_obj(self, i):
        print "obj %d" % i
        return "result"

Myclass(nobj=8, workers=3)
# problem !!! the destructor is called nobj times (instead of once), 
# **and** results are empty !

Donc, aucune des deux méthodes n'est satisfaisante ...

20
Eric H.

Il existe un autre raccourci que vous pouvez utiliser, bien que cela puisse être inefficace en fonction du contenu de vos instances de classe.

Comme tout le monde l’a dit, le problème est que le code multiprocessing doit consigner les éléments qu’il envoie aux sous-processus qu’il a démarrés et que le préparateur ne fait pas d’instance-méthodes.

Cependant, au lieu d’envoyer la méthode-instance, vous pouvez envoyer l’instance réelle de la classe, ainsi que le nom de la fonction à appeler, à une fonction ordinaire qui utilise ensuite getattr pour appeler la méthode-instance, créant ainsi le lien. méthode dans le sous-processus Pool. Ceci est similaire à la définition d'une méthode __call__ sauf que vous pouvez appeler plusieurs fonctions membres.

Voler le code de @ EricH. Dans sa réponse et l'annoter un peu (je l'ai ressaisi donc tous les changements de nom, etc., pour une raison quelconque, cela semblait plus facile que couper-coller :-)) pour illustrer toute la magie:

import multiprocessing
import os

def call_it(instance, name, args=(), kwargs=None):
    "indirect caller for instance methods and multiprocessing"
    if kwargs is None:
        kwargs = {}
    return getattr(instance, name)(*args, **kwargs)

class Klass(object):
    def __init__(self, nobj, workers=multiprocessing.cpu_count()):
        print "Constructor (in pid=%d)..." % os.getpid()
        self.count = 1
        pool = multiprocessing.Pool(processes = workers)
        async_results = [pool.apply_async(call_it,
            args = (self, 'process_obj', (i,))) for i in range(nobj)]
        pool.close()
        map(multiprocessing.pool.ApplyResult.wait, async_results)
        lst_results = [r.get() for r in async_results]
        print lst_results

    def __del__(self):
        self.count -= 1
        print "... Destructor (in pid=%d) count=%d" % (os.getpid(), self.count)

    def process_obj(self, index):
        print "object %d" % index
        return "results"

Klass(nobj=8, workers=3)

La sortie montre que le constructeur est appelé une fois (dans le pid original) et le destructeur est appelé 9 fois (une fois pour chaque copie effectuée = 2 ou 3 fois par processus pool-worker-process selon les besoins, plus une fois dans l’original). processus). Ceci est souvent correct, comme dans ce cas, car le sélecteur par défaut crée une copie de l'instance entière et la recompose (semi-) secrètement - en l'occurrence, en effectuant:

obj = object.__new__(Klass)
obj.__dict__.update({'count':1})

C'est pourquoi, même si le destructeur est appelé huit fois dans les trois processus de travail, il compte à rebours de 1 à 0 à chaque fois, mais vous pouvez bien sûr avoir des ennuis de cette façon. Si nécessaire, vous pouvez fournir votre propre __setstate__:

    def __setstate__(self, adict):
        self.count = adict['count']

dans ce cas par exemple.

14
torek

Vous pouvez également définir une méthode __call__() à l'intérieur de votre someClass(), qui appelle someClass.go(), puis transmet une instance de someClass() au pool. Cet objet est décapable et cela fonctionne bien (pour moi) ...

class someClass(object):
   def __init__(self):
       pass
   def f(self, x):
       return x*x

   def go(self):
      p = Pool(4)
      sc = p.map(self, range(4))
      print sc

   def __call__(self, x):   
     return self.f(x)

sc = someClass()
sc.go()
10
parisjohn

La solution de parisjohn ci-dessus me convient parfaitement. De plus, le code semble propre et facile à comprendre. Dans mon cas, il y a quelques fonctions à appeler avec Pool, j'ai donc modifié le code de parisjohn un peu plus bas. J'ai fait appeler pour pouvoir appeler plusieurs fonctions, et les noms de fonction sont passés dans l'argument dict de go():

from multiprocessing import Pool
class someClass(object):
    def __init__(self):
        pass

    def f(self, x):
        return x*x

    def g(self, x):
        return x*x+1    

    def go(self):
        p = Pool(4)
        sc = p.map(self, [{"func": "f", "v": 1}, {"func": "g", "v": 2}])
        print sc

    def __call__(self, x):
        if x["func"]=="f":
            return self.f(x["v"])
        if x["func"]=="g":
            return self.g(x["v"])        

sc = someClass()
sc.go()
2
neobot

Pourquoi ne pas utiliser des fonctions séparées?

def func(*args, **kwargs):
    return inst.method(args, kwargs)

print pool.map(func, arr)
1
0script0

J'ai rencontré le même problème, mais j'ai découvert qu'il existe un encodeur JSON qui peut être utilisé pour déplacer ces objets entre les processus.

from pyVmomi.VmomiSupport import VmomiJSONEncoder

Utilisez ceci pour créer votre liste: jsonSerialized= json.dumps(pfVmomiObj, cls=VmomiJSONEncoder)

Ensuite, dans la fonction mappée, utilisez-la pour récupérer l'objet: pfVmomiObj = json.loads(jsonSerialized)

1
George

Une solution potentiellement triviale consiste à utiliser multiprocessing.dummy. Ceci est une implémentation basée sur les threads de l'interface de multitraitement qui ne semble pas avoir ce problème dans Python 2.7. Je n'ai pas beaucoup d'expérience ici, mais ce changement d'importation rapide m'a permis d'appeler apply_async sur une méthode de classe.

Quelques bonnes ressources sur multiprocessing.dummy:

https://docs.python.org/2/library/multiprocessing.html#module-multiprocessing.dummy

http://chriskiehl.com/article/parallelism-in-one-line/

1
David Parks

Dans ce cas simple, où someClass.f n'hérite d'aucune donnée de la classe et n'attache rien à la classe, une solution possible consisterait à séparer f, afin qu'elle puisse être conservée:

import multiprocessing


def f(x):
    return x*x


class someClass(object):
    def __init__(self):
        pass

    def go(self):
        pool = multiprocessing.Pool(processes=4)       
        print pool.map(f, range(10))
1
mhh

Mise à jour: à compter de la date de rédaction de ce document, les Tubes nommés sont sélectionnables (à partir de python 2.7)

Le problème ici est que les processus enfants ne sont pas en mesure d'importer la classe de l'objet - dans ce cas, la classe P-, dans le cas d'un projet multimodèle, la classe P doit pouvoir être importée partout où le processus enfant est utilisé.

une solution rapide consiste à le rendre importable en l’affectant à globals ()

globals()["P"] = P
0
rachid el kedmiri