web-dev-qa-db-fra.com

Appliquer une méthode à une liste d'objets en parallèle à l'aide de multi-traitements

J'ai créé une classe avec un certain nombre de méthodes. Une des méthodes prend beaucoup de temps, my_process, et je voudrais faire cette méthode en parallèle. Je suis tombé sur Python Multiprocessing - appliquez une méthode de classe à une liste d'objets mais je ne sais pas comment l'appliquer à mon problème ni quel effet cela aura sur les autres méthodes de ma classe.

class MyClass():
    def __init__(self, input):
        self.input = input
        self.result = int

    def my_process(self, multiply_by, add_to):
        self.result = self.input * multiply_by
        self._my_sub_process(add_to)
        return self.result

    def _my_sub_process(self, add_to):
        self.result += add_to

list_of_numbers = range(0, 5)
list_of_objects = [MyClass(i) for i in list_of_numbers]
list_of_results = [obj.my_process(100, 1) for obj in list_of_objects] # multi-process this for-loop

print list_of_numbers
print list_of_results

[0, 1, 2, 3, 4]
[1, 101, 201, 301, 401]
12
bluprince13

Je vais aller à contre-courant et suggérer de s'en tenir à la chose la plus simple qui puisse fonctionner ;-) Autrement dit, les fonctions de type Pool.map()- sont idéales pour cela, mais se limitent à transmettre un seul argument. Plutôt que de faire des efforts héroïques pour contourner ce problème, écrivez simplement une fonction d'assistance qui n'a besoin que d'un seul argument: un tuple. Ensuite, tout est facile et clair.

Voici un programme complet utilisant cette approche, qui affiche ce que vous voulez sous Python 2, et quel que soit le système d'exploitation:

class MyClass():
    def __init__(self, input):
        self.input = input
        self.result = int

    def my_process(self, multiply_by, add_to):
        self.result = self.input * multiply_by
        self._my_sub_process(add_to)
        return self.result

    def _my_sub_process(self, add_to):
        self.result += add_to

import multiprocessing as mp
NUM_CORE = 4  # set to the number of cores you want to use

def worker(arg):
    obj, m, a = arg
    return obj.my_process(m, a)

if __== "__main__":
    list_of_numbers = range(0, 5)
    list_of_objects = [MyClass(i) for i in list_of_numbers]

    pool = mp.Pool(NUM_CORE)
    list_of_results = pool.map(worker, ((obj, 100, 1) for obj in list_of_objects))
    pool.close()
    pool.join()

    print list_of_numbers
    print list_of_results

Un grand de magie

Il convient de noter que l’approche très simple que je propose présente de nombreux avantages. Au-delà, cela "fonctionne tout simplement" sur Pythons 2 et 3, ne nécessite aucune modification de vos classes et est facile à comprendre. Il joue également à Nice avec toutes les méthodes Pool.

Cependant, si vous souhaitez exécuter plusieurs méthodes en parallèle, il peut être un peu gênant d'écrire une petite fonction de travail pour chacune d'elles. Alors, voici un tout petit peu de "magie" à éviter. Changer worker() comme suit:

def worker(arg):
    obj, methname = arg[:2]
    return getattr(obj, methname)(*arg[2:])

Maintenant, une seule fonction worker suffit pour un nombre quelconque de méthodes, avec un nombre quelconque d'arguments. Dans votre cas spécifique, il suffit de modifier une ligne pour qu'elle corresponde à:

list_of_results = pool.map(worker, ((obj, "my_process", 100, 1) for obj in list_of_objects))

Des généralisations plus ou moins évidentes peuvent également s’adapter aux méthodes avec des arguments de mots clés. Mais, dans la vraie vie, je généralement je m'en tiens à la suggestion initiale. À un moment donné, traiter avec des généralisations fait plus de mal que de bien. Encore une fois, j'aime les choses évidentes ;-)

10
Tim Peters

Généralement, la méthode la plus simple pour exécuter le même calcul en parallèle est la méthode map d'un multiprocessing.Pool (ou la fonction as_completed de concurrent.futures en Python 3).

Cependant, la méthode map applique une fonction qui ne prend qu'un seul argument à un itérable de données utilisant plusieurs processus.

Donc, cette fonction ne peut pas être une méthode normale, car elle nécessite au moins deux arguments; il doit également inclure self! Ce pourrait être une méthode statique, cependant. Voir aussi cette réponse pour une explication plus détaillée.

2
Roland Smith

Si votre classe n’est pas "énorme", je pense que mieux vaut orienter le processus vers le processus . La piscine en multitraitement est suggérée.
Ceci est le tutoriel -> https://docs.python.org/2/library/multiprocessing.html#using-a-pool-of-workers

Puis séparez le add_to de my_process car ils sont rapides et vous pouvez attendre la fin du dernier processus.

def my_process(input, multiby):
    return xxxx
def add_to(result,a_list):
    xxx
p = Pool(5)
res = []
for i in range(10):
    res.append(p.apply_async(my_process, (i,5)))
p.join()  # wait for the end of the last process
for i in range(10):
    print res[i].get()
1
Zealseeker

Si vous n’avez absolument pas besoin de vous en tenir au module de multitraitement, alors, il peut être facilement réalisé avec concurrents.futures library.

voici l'exemple de code:

from concurrent.futures.thread import ThreadPoolExecutor, wait

MAX_WORKERS = 20

class MyClass():
    def __init__(self, input):
        self.input = input
        self.result = int

    def my_process(self, multiply_by, add_to):
        self.result = self.input * multiply_by
        self._my_sub_process(add_to)
        return self.result

    def _my_sub_process(self, add_to):
        self.result += add_to

list_of_numbers = range(0, 5)
list_of_objects = [MyClass(i) for i in list_of_numbers]

With ThreadPoolExecutor(MAX_WORKERS) as executor:
    for obj in list_of_objects:
        executor.submit(obj.my_process, 100, 1).add_done_callback(on_finish)

def on_finish(future):
    result = future.result() # do stuff with your result

ici, l'exécuteur renvoie le futur pour chaque tâche qu'il soumet. N'oubliez pas que si vous utilisez la fonction add_done_callback() terminée, la tâche du thread retourne au thread principal (ce qui bloquerait votre thread main) si vous voulez vraiment du vrai parallélisme, attendez les objets futurs séparément. voici l'extrait de code pour cela.

futures = []
with ThreadPoolExecutor(MAX_WORKERS) as executor:
    for objin list_of_objects:
        futures.append(executor.submit(obj.my_process, 100, 1))
wait(futures)

for succeded, failed in futures:
    # work with your result here
    if succeded:
       print (succeeeded.result())
    if failed:
        print (failed.result())

j'espère que cela t'aides.

0
Asav Patel

Basé sur la réponse de Python Multiprocessing - Appliquer la méthode de classe à une liste d'objets et votre code 

  1. ajouter MyClass object dans simulation object

    class simulation(multiprocessing.Process):
        def __init__(self, id, worker, *args, **kwargs):
            # must call this before anything else
            multiprocessing.Process.__init__(self)
            self.id = id
            self.worker = worker
            self.args = args
            self.kwargs = kwargs
            sys.stdout.write('[%d] created\n' % (self.id))
    
  2. lancez ce que vous voulez dans la fonction run

        def run(self):
            sys.stdout.write('[%d] running ...  process id: %s\n' % (self.id, os.getpid()))
            self.worker.my_process(*self.args, **self.kwargs)
            sys.stdout.write('[%d] completed\n' % (self.id))
    

Essaye ça:

list_of_numbers = range(0, 5)
list_of_objects = [MyClass(i) for i in list_of_numbers]
list_of_sim = [simulation(id=k, worker=obj, multiply_by=100*k, add_to=10*k) \
    for k, obj in enumerate(list_of_objects)]  

for sim in list_of_sim:
    sim.start()
0
Huu-Danh Pham