web-dev-qa-db-fra.com

Exécuter des tâches en parallèle dans python

J'utilise python 2.7, j'ai du code qui ressemble à ceci:

task1()
task2()
task3()
dependent1()

task4()
task5()
task6()
dependent2()

dependent3()

Les seules dépendances ici sont les suivantes: dependante1 doit attendre les tâches 1-3, dependante2 doit attendre les tâches 4-6 et dependante3 doit attendre les dépendantes1-2 ... Ce serait bien: exécuter les 6 tâches en premier en parallèle, puis les deux premières personnes à charge en parallèle .. puis la dernière personne à charge

Je préfère avoir autant de tâches que possible en parallèle, j'ai cherché sur Google pour certains modules mais j'espérais éviter les bibliothèques externes, et je ne sais pas comment la technique de file d'attente peut résoudre mon problème (peut-être que quelqu'un peut recommander une bonne ressource ?)

27
Mohamed Khamis

La classe intégrée threading.Thread offre tout ce dont vous avez besoin: start pour démarrer un nouveau thread et join pour attendre la fin d'un thread.

import threading

def task1():
    pass
def task2():
    pass
def task3():
    pass
def task4():
    pass
def task5():
    pass
def task6():
    pass

def dep1():
    t1 = threading.Thread(target=task1)
    t2 = threading.Thread(target=task2)
    t3 = threading.Thread(target=task3)

    t1.start()
    t2.start()
    t3.start()

    t1.join()
    t2.join()
    t3.join()

def  dep2():
    t4 = threading.Thread(target=task4)
    t5 = threading.Thread(target=task5)

    t4.start()
    t5.start()

    t4.join()
    t5.join()

def dep3():
    d1 = threading.Thread(target=dep1)
    d2 = threading.Thread(target=dep2)

    d1.start()
    d2.start()

    d1.join()
    d2.join()

d3 = threading.Thread(target=dep3)
d3.start()
d3.join()

Sinon, pour rejoindre, vous pouvez utiliser Queue.join pour attendre la fin des threads.

33
gecco

Si vous êtes prêt à essayer les bibliothèques externes, vous pouvez exprimer les tâches et leurs dépendances avec élégance avec Ray . Cela fonctionne bien sur une seule machine, l'avantage ici est que le parallélisme et les dépendances peuvent être plus faciles à exprimer avec Ray qu'avec le python multiprocessing et qu'il n'a pas le problème GIL (Global Interpreter Lock)) cela empêche souvent le multithreading de fonctionner efficacement. De plus, il est très facile d'augmenter la charge de travail sur un cluster si vous en avez besoin à l'avenir.

La solution ressemble à ceci:

import ray

ray.init()

@ray.remote
def task1():
    pass

@ray.remote
def task2():
    pass

@ray.remote
def task3():
    pass

@ray.remote
def dependent1(x1, x2, x3):
    pass

@ray.remote
def task4():
    pass

@ray.remote
def task5():
    pass

@ray.remote
def task6():
    pass

@ray.remote
def dependent2(x1, x2, x3):
    pass

@ray.remote
def dependent3(x, y):
    pass

id1 = task1.remote()
id2 = task2.remote()
id3 = task3.remote()

dependent_id1 = dependent1.remote(id1, id2, id3)

id4 = task4.remote()
id5 = task5.remote()
id6 = task6.remote()

dependent_id2 = dependent2.remote(id4, id5, id6)

dependent_id3 = dependent3.remote(dependent_id1, dependent_id2)

ray.get(dependent_id3) # This is optional, you can get the results if the tasks return an object

Vous pouvez également passer des objets python réels entre les tâches en utilisant les arguments à l'intérieur des tâches et en renvoyant les résultats (par exemple en disant "valeur de retour" au lieu de "passer" ci-dessus).

En utilisant "pip install ray", le code ci-dessus fonctionne dès le départ sur une seule machine, et il est également facile de paralléliser des applications sur un cluster, que ce soit dans le cloud ou dans votre propre cluster personnalisé, voir https: // ray.readthedocs.io/en/latest/autoscaling.html et https://ray.readthedocs.io/en/latest/using-ray-on-a-cluster.html ) . Cela pourrait être utile si votre charge de travail augmente plus tard.

Avertissement: je suis l'un des développeurs de Ray.

2
Philipp Moritz

Regardez Gevent .

Exemple d'utilisation:

import gevent
from gevent import socket

def destination(jobs):
    gevent.joinall(jobs, timeout=2)
    print [job.value for job in jobs]

def task1():
    return gevent.spawn(socket.gethostbyname, 'www.google.com')

def task2():
    return gevent.spawn(socket.gethostbyname, 'www.example.com')

def task3():
    return gevent.spawn(socket.gethostbyname, 'www.python.org')

jobs = []
jobs.append(task1())
jobs.append(task2())
jobs.append(task3())
destination(jobs)

J'espère que c'est ce que vous cherchiez.

1
meson10