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.
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))
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]))
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]
À 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 . :)
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))
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))
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()
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
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)
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.
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
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 pickle
able 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.
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
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)
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()
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)