web-dev-qa-db-fra.com

Multitraitement: Comment utiliser Pool.map sur une fonction définie dans une classe?

Quand je lance quelque chose comme:

from multiprocessing import Pool

p = Pool(5)
def f(x):
     return x*x

p.map(f, [1,2,3])

ça fonctionne bien. Cependant, en mettant cela en fonction d'une classe:

class calculate(object):
    def run(self):
        def f(x):
            return x*x

        p = Pool()
        return p.map(f, [1,2,3])

cl = calculate()
print cl.run()

Me donne l'erreur suivante:

Exception in thread Thread-1:
Traceback (most recent call last):
  File "/sw/lib/python2.6/threading.py", line 532, in __bootstrap_inner
    self.run()
  File "/sw/lib/python2.6/threading.py", line 484, in run
    self.__target(*self.__args, **self.__kwargs)
  File "/sw/lib/python2.6/multiprocessing/pool.py", line 225, in _handle_tasks
    put(task)
PicklingError: Can't pickle <type 'function'>: attribute lookup __builtin__.function failed

J'ai lu un article d'Alex Martelli traitant du même type de problème, mais ce n'était pas assez explicite.

162
Mermoz

J'étais aussi agacé par les restrictions sur le type de fonctions que pool.map pouvait accepter. J'ai écrit ce qui suit pour contourner cela. Cela semble fonctionner, même pour une utilisation récursive de parmap.

from multiprocessing import Process, Pipe
from itertools import izip

def spawn(f):
    def fun(pipe,x):
        pipe.send(f(x))
        pipe.close()
    return fun

def parmap(f,X):
    pipe=[Pipe() for x in X]
    proc=[Process(target=spawn(f),args=(c,x)) for x,(p,c) in izip(X,pipe)]
    [p.start() for p in proc]
    [p.join() for p in proc]
    return [p.recv() for (p,c) in pipe]

if __== '__main__':
    print parmap(lambda x:x**x,range(1,5))
67
mrule

Je ne pouvais pas utiliser les codes publiés jusqu'à présent car les codes utilisant "multitraitement.Pool" ne fonctionnent pas avec les expressions lambda et les codes n'utilisant pas "multitraitement.Pool" génèrent autant de processus qu'il y a d'éléments de travail.

J'ai adapté le code à il génère un nombre prédéfini de travailleurs et ne se répète que dans la liste de saisie s'il existe un travailleur inactif. J'ai également activé le mode "démon" pour les travailleurs à la fois. ctrl-c fonctionne comme prévu.

import multiprocessing


def fun(f, q_in, q_out):
    while True:
        i, x = q_in.get()
        if i is None:
            break
        q_out.put((i, f(x)))


def parmap(f, X, nprocs=multiprocessing.cpu_count()):
    q_in = multiprocessing.Queue(1)
    q_out = multiprocessing.Queue()

    proc = [multiprocessing.Process(target=fun, args=(f, q_in, q_out))
            for _ in range(nprocs)]
    for p in proc:
        p.daemon = True
        p.start()

    sent = [q_in.put((i, x)) for i, x in enumerate(X)]
    [q_in.put((None, None)) for _ in range(nprocs)]
    res = [q_out.get() for _ in range(len(sent))]

    [p.join() for p in proc]

    return [x for i, x in sorted(res)]


if __== '__main__':
    print(parmap(lambda i: i * 2, [1, 2, 3, 4, 6, 7, 8]))
81
klaus se

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. En effet, dill est utilisé à la place de pickle ou cPickle, et dill peut sérialiser presque tout ce qui se trouve en python.

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

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

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

Il gère même le code que vous avez écrit initialement, sans modification, et à partir de l'interprète. Pourquoi faire autre chose qui est plus fragile et spécifique à un seul cas?

>>> from pathos.multiprocessing import ProcessingPool as Pool
>>> class calculate(object):
...  def run(self):
...   def f(x):
...    return x*x
...   p = Pool()
...   return p.map(f, [1,2,3])
... 
>>> cl = calculate()
>>> print cl.run()
[1, 4, 9]

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

Et, juste pour montrer un peu plus de ce qu'il peut faire:

>>> from pathos.multiprocessing import ProcessingPool as Pool
>>> 
>>> p = Pool(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]
>>> 
>>> res = p.amap(t.plus, x, y)
>>> res.get()
[4, 6, 8, 10]
46
Mike McKerns

À ce que je sache, il n’existe actuellement aucune solution à votre problème: la fonction que vous donnez à map() doit être accessible via une importation de votre module. C'est pourquoi le code de robert fonctionne: la fonction f() peut être obtenue en important le code suivant:

def f(x):
    return x*x

class Calculate(object):
    def run(self):
        p = Pool()
        return p.map(f, [1,2,3])

if __== '__main__':
    cl = Calculate()
    print cl.run()

J'ai en fait ajouté une section "main", car elle suit le recommandations pour la plate-forme Windows ("Assurez-vous que le module principal peut être importé en toute sécurité par un nouveau Python interprète sans provoquer d’effets secondaires non désirés ").

J'ai aussi ajouté une lettre majuscule devant Calculate, afin de suivre PEP 8 . :)

39
Eric O Lebigot

La solution de mrule est correcte mais présente un bogue: si l'enfant renvoie une grande quantité de données, il peut remplir le tampon du tube en bloquant la pipe.send() de l'enfant, pendant que le parent attend que l'enfant quitter sur pipe.join(). La solution consiste à lire les données de l'enfant avant la join() de l'enfant. De plus, l'enfant doit fermer l'extrémité du tuyau du parent pour éviter une impasse. Le code ci-dessous corrige cela. Sachez également que cette parmap crée un processus par élément dans X. Une solution plus avancée consiste à utiliser multiprocessing.cpu_count() pour diviser X en plusieurs morceaux, puis à fusionner les résultats avant de renvoyer. Je laisse cela comme un exercice au lecteur afin de ne pas gâcher la concision de la réponse de Nice par mrule. ;)

from multiprocessing import Process, Pipe
from itertools import izip

def spawn(f):
    def fun(ppipe, cpipe,x):
        ppipe.close()
        cpipe.send(f(x))
        cpipe.close()
    return fun

def parmap(f,X):
    pipe=[Pipe() for x in X]
    proc=[Process(target=spawn(f),args=(p,c,x)) for x,(p,c) in izip(X,pipe)]
    [p.start() for p in proc]
    ret = [p.recv() for (p,c) in pipe]
    [p.join() for p in proc]
    return ret

if __== '__main__':
    print parmap(lambda x:x**x,range(1,5))
18
Bob McElrath

J'ai aussi eu du mal avec ça. J'ai eu des fonctions en tant que membres de données d'une classe, à titre d'exemple simplifié:

from multiprocessing import Pool
import itertools
pool = Pool()
class Example(object):
    def __init__(self, my_add): 
        self.f = my_add  
    def add_lists(self, list1, list2):
        # Needed to do something like this (the following line won't work)
        return pool.map(self.f,list1,list2)  

J'avais besoin d'utiliser la fonction self.f dans un appel Pool.map () de la même classe et self.f ne prenait pas un Tuple en argument. Comme cette fonction était intégrée à une classe, il n’était pas clair pour moi d’écrire le type de wrapper que les autres réponses suggérées.

J'ai résolu ce problème en utilisant un wrapper différent qui prend un Tuple/liste, où le premier élément est la fonction et les éléments restants sont les arguments de cette fonction, appelée eval_func_Tuple (f_args). En utilisant cela, la ligne problématique peut être remplacée par return pool.map (eval_func_Tuple, itertools.izip (itertools.repeat (self.f), list1, list2)). Voici le code complet:

Fichier: util.py

def add(a, b): return a+b

def eval_func_Tuple(f_args):
    """Takes a Tuple of a function and args, evaluates and returns result"""
    return f_args[0](*f_args[1:])  

Fichier: main.py

from multiprocessing import Pool
import itertools
import util  

pool = Pool()
class Example(object):
    def __init__(self, my_add): 
        self.f = my_add  
    def add_lists(self, list1, list2):
        # The following line will now work
        return pool.map(util.eval_func_Tuple, 
            itertools.izip(itertools.repeat(self.f), list1, list2)) 

if __== '__main__':
    myExample = Example(util.add)
    list1 = [1, 2, 3]
    list2 = [10, 20, 30]
    print myExample.add_lists(list1, list2)  

Lancer main.py donnera [11, 22, 33]. N'hésitez pas à l'améliorer, par exemple, eval_func_Tuple pourrait également être modifié pour prendre des arguments de mots clés.

Sur une autre note, dans une autre réponse, la fonction "parmap" peut être rendue plus efficace lorsque le nombre de processus est supérieur au nombre de processeurs disponibles. Je copie une version modifiée ci-dessous. Ceci est mon premier message et je ne savais pas si je devrais modifier directement la réponse originale. J'ai aussi renommé certaines variables.

from multiprocessing import Process, Pipe  
from itertools import izip  

def spawn(f):  
    def fun(pipe,x):  
        pipe.send(f(x))  
        pipe.close()  
    return fun  

def parmap(f,X):  
    pipe=[Pipe() for x in X]  
    processes=[Process(target=spawn(f),args=(c,x)) for x,(p,c) in izip(X,pipe)]  
    numProcesses = len(processes)  
    processNum = 0  
    outputList = []  
    while processNum < numProcesses:  
        endProcessNum = min(processNum+multiprocessing.cpu_count(), numProcesses)  
        for proc in processes[processNum:endProcessNum]:  
            proc.start()  
        for proc in processes[processNum:endProcessNum]:  
            proc.join()  
        for proc,c in pipe[processNum:endProcessNum]:  
            outputList.append(proc.recv())  
        processNum = endProcessNum  
    return outputList    

if __== '__main__':  
    print parmap(lambda x:x**x,range(1,5))         
13
Brandt

Les fonctions définies dans les classes (même dans les fonctions au sein des classes) ne décoiffent pas vraiment. Cependant, cela fonctionne:

def f(x):
    return x*x

class calculate(object):
    def run(self):
        p = Pool()
    return p.map(f, [1,2,3])

cl = calculate()
print cl.run()
7
robert

J'ai pris la réponse de klaus se et aganders3 et créé un module documenté, plus lisible et contenant un fichier. Vous pouvez simplement l'ajouter à votre projet. Il a même une barre de progression optionnelle!

"""
The ``processes`` module provides some convenience functions
for using parallel processes in python.

Adapted from http://stackoverflow.com/a/16071616/287297

Example usage:

    print prll_map(lambda i: i * 2, [1, 2, 3, 4, 6, 7, 8], 32, verbose=True)

Comments:

"It spawns a predefined amount of workers and only iterates through the input list
 if there exists an idle worker. I also enabled the "daemon" mode for the workers so
 that KeyboardInterupt works as expected."

Pitfalls: all the stdouts are sent back to the parent stdout, intertwined.

Alternatively, use this fork of multiprocessing: 
https://github.com/uqfoundation/multiprocess
"""

# Modules #
import multiprocessing
from tqdm import tqdm

################################################################################
def apply_function(func_to_apply, queue_in, queue_out):
    while not queue_in.empty():
        num, obj = queue_in.get()
        queue_out.put((num, func_to_apply(obj)))

################################################################################
def prll_map(func_to_apply, items, cpus=None, verbose=False):
    # Number of processes to use #
    if cpus is None: cpus = min(multiprocessing.cpu_count(), 32)
    # Create queues #
    q_in  = multiprocessing.Queue()
    q_out = multiprocessing.Queue()
    # Process list #
    new_proc  = lambda t,a: multiprocessing.Process(target=t, args=a)
    processes = [new_proc(apply_function, (func_to_apply, q_in, q_out)) for x in range(cpus)]
    # Put all the items (objects) in the queue #
    sent = [q_in.put((i, x)) for i, x in enumerate(items)]
    # Start them all #
    for proc in processes:
        proc.daemon = True
        proc.start()
    # Display progress bar or not #
    if verbose:
        results = [q_out.get() for x in tqdm(range(len(sent)))]
    else:
        results = [q_out.get() for x in range(len(sent))]
    # Wait for them to finish #
    for proc in processes: proc.join()
    # Return results #
    return [x for i, x in sorted(results)]

################################################################################
def test():
    def slow_square(x):
        import time
        time.sleep(2)
        return x**2
    objs    = range(20)
    squares = prll_map(slow_square, objs, 4, verbose=True)
    print "Result: %s" % squares

EDIT : Ajout de la suggestion @ alexander-mcfarlane et d'une fonction de test

7
xApple

Je sais que cela a été demandé il y a plus de 6 ans maintenant, mais je voulais juste ajouter ma solution, car certaines des suggestions ci-dessus semblent horriblement compliquées, mais ma solution était en réalité très simple.

Tout ce que j'avais à faire était d'envelopper l'appel pool.map () à une fonction d'assistance. Passer l'objet de classe avec les arguments de la méthode en tant que Tuple, ce qui ressemblait un peu à ceci.

def run_in_parallel(args):
    return args[0].method(args[1])

myclass = MyClass()
method_args = [1,2,3,4,5,6]
args_map = [ (myclass, arg) for arg in method_args ]
pool = Pool()
pool.map(run_in_parallel, args_map)
6
nightowl

J'ai modifié la méthode de klaus se car, même si elle fonctionnait avec de petites listes, elle se bloquait lorsque le nombre d'éléments était égal ou supérieur à 1 000. Plutôt que de pousser les travaux un à un avec la condition None stop, je charge la file d’entrée en une seule fois et laisse les processus y travailler jusqu’à ce qu’elle soit vide.

from multiprocessing import cpu_count, Queue, Process

def apply_func(f, q_in, q_out):
    while not q_in.empty():
        i, x = q_in.get()
        q_out.put((i, f(x)))

# map a function using a pool of processes
def parmap(f, X, nprocs = cpu_count()):
    q_in, q_out   = Queue(), Queue()
    proc = [Process(target=apply_func, args=(f, q_in, q_out)) for _ in range(nprocs)]
    sent = [q_in.put((i, x)) for i, x in enumerate(X)]
    [p.start() for p in proc]
    res = [q_out.get() for _ in sent]
    [p.join() for p in proc]

    return [x for i,x in sorted(res)]

Edit: malheureusement, je rencontre maintenant cette erreur sur mon système: La limite de taille maximale de la file d'attente de traitement est de 32767 , espérons que les solutions de contournement seront utiles.

3
aganders3

Je sais que cette question a été posée il y a 8 ans et 10 mois mais je souhaite vous présenter ma solution:

from multiprocessing import Pool

class Test:

    def __init__(self):
        self.main()

    @staticmethod
    def methodForMultiprocessing(x):
        print(x*x)

    def main(self):
        if __== "__main__":
            p = Pool()
            p.map(Test.methodForMultiprocessing, list(range(1, 11)))
            p.close()

TestObject = Test()

Vous avez juste besoin de transformer votre classe en une fonction statique. Mais c'est aussi possible avec une méthode de classe:

from multiprocessing import Pool

class Test:

    def __init__(self):
        self.main()

    @classmethod
    def methodForMultiprocessing(cls, x):
        print(x*x)

    def main(self):
        if __== "__main__":
            p = Pool()
            p.map(Test.methodForMultiprocessing, list(range(1, 11)))
            p.close()

TestObject = Test()

Testé dans Python 3.7.3

1
TornaxO7

Vous pouvez exécuter votre code sans problème si vous ignorez manuellement l’objet Pool de la liste des objets de la classe, car il n’est pas pickleable comme le dit l’erreur. Vous pouvez le faire avec le __getstate__ _ fonction (regardez ici aussi) comme suit. L’objet Pool essaiera de trouver le __getstate__ et __setstate__ fonctions et les exécuter s’il le trouve lorsque vous exécutez map, map_async etc:

class calculate(object):
    def __init__(self):
        self.p = Pool()
    def __getstate__(self):
        self_dict = self.__dict__.copy()
        del self_dict['p']
        return self_dict
    def __setstate__(self, state):
        self.__dict__.update(state)

    def f(self, x):
        return x*x
    def run(self):
        return self.p.map(self.f, [1,2,3])

Alors fais:

cl = calculate()
cl.run()

vous donnera la sortie:

[1, 4, 9]

J'ai testé le code ci-dessus dans Python 3.x et cela fonctionne.

0
Amir

Je ne sais pas si cette approche a été adoptée, mais voici un moyen de contourner ce que j'utilise:

from multiprocessing import Pool

t = None

def run(n):
    return t.f(n)

class Test(object):
    def __init__(self, number):
        self.number = number

    def f(self, x):
        print x * self.number

    def pool(self):
        pool = Pool(2)
        pool.map(run, range(10))

if __== '__main__':
    t = Test(9)
    t.pool()
    pool = Pool(2)
    pool.map(run, range(10))

La sortie devrait être:

0
9
18
27
36
45
54
63
72
81
0
9
18
27
36
45
54
63
72
81
0
CpILL

Voici ma solution, qui me semble un peu moins rigoureuse que la plupart des autres ici. C'est semblable à la réponse de nightowl.

someclasses = [MyClass(), MyClass(), MyClass()]

def method_caller(some_object, some_method='the method'):
    return getattr(some_object, some_method)()

othermethod = partial(method_caller, some_method='othermethod')

with Pool(6) as pool:
    result = pool.map(othermethod, someclasses)
0
Erlend Aune

De http://www.rueckstiess.net/research/snippets/show/ca1d7d9 et http://qingkaikong.blogspot.com/2016/python-parallel-method- in-class.html

Nous pouvons créer une fonction externe et l'ensemencer avec l'objet auto de classe:

from joblib import Parallel, delayed
def unwrap_self(arg, **kwarg):
    return square_class.square_int(*arg, **kwarg)

class square_class:
    def square_int(self, i):
        return i * i

    def run(self, num):
        results = []
        results = Parallel(n_jobs= -1, backend="threading")\
            (delayed(unwrap_self)(i) for i in Zip([self]*len(num), num))
        print(results)

OU sans emploi:

from multiprocessing import Pool
import time

def unwrap_self_f(arg, **kwarg):
    return C.f(*arg, **kwarg)

class C:
    def f(self, name):
        print 'hello %s,'%name
        time.sleep(5)
        print 'Nice to meet you.'

    def run(self):
        pool = Pool(processes=2)
        names = ('frank', 'justin', 'osi', 'thomas')
        pool.map(unwrap_self_f, Zip([self]*len(names), names))

if __== '__main__':
    c = C()
    c.run()
0
Bob Baxley
class Calculate(object):
  # Your instance method to be executed
  def f(self, x, y):
    return x*y

if __== '__main__':
  inp_list = [1,2,3]
  y = 2
  cal_obj = Calculate()
  pool = Pool(2)
  results = pool.map(lambda x: cal_obj.f(x, y), inp_list)

Il est possible que vous souhaitiez appliquer cette fonction à chaque instance différente de la classe. Alors voici la solution pour ça aussi

class Calculate(object):
  # Your instance method to be executed
  def __init__(self, x):
    self.x = x

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

if __== '__main__':
  inp_list = [Calculate(i) for i in range(3)]
  y = 2
  pool = Pool(2)
  results = pool.map(lambda x: x.f(y), inp_list)
0
ShikharDua