web-dev-qa-db-fra.com

Python Processus ou pool de multitraitement pour ce que je fais?

Je suis nouveau dans le multitraitement en Python et j'essaie de savoir si je dois utiliser Pool ou Process pour appeler deux fonctions asynchrones. Les deux fonctions que je fais font des appels curl et analysent les informations dans un 2 listes distinctes. En fonction de la connexion Internet, chaque fonction peut prendre environ 4 secondes chacune. Je me rends compte que le goulot d'étranglement est dans la connexion ISP et le multitraitement ne l'accélérera pas beaucoup, mais ce serait bien de les faire démarrer asynchrones De plus, c'est une grande expérience d'apprentissage pour moi d'entrer dans le multi-traitement de python parce que je l'utiliserai plus tard.

J'ai lu Python multiprocessing.Pool: quand utiliser apply, apply_async ou map? et c'était utile, mais j'avais toujours mes propres questions.

Donc, je pourrais le faire:

def foo():
    pass

def bar():
    pass

p1 = Process(target=foo, args=())
p2 = Process(target=bar, args=())

p1.start()
p2.start()
p1.join()
p2.join()

Les questions que j'ai pour cette implémentation sont les suivantes: 1) Puisque les blocs de jointure jusqu'à ce que le processus d'appel soit terminé ... cela signifie-t-il que le processus p1 doit se terminer avant le démarrage du processus p2? J'ai toujours compris que le .join () était le même que pool.apply () et pool.apply_sync (). Get () où le processus parent ne peut pas lancer un autre processus (tâche) tant que celui en cours d'exécution n'est pas terminé.

L'autre alternative serait quelque chose comme:

def foo():
    pass

def bar():
    pass
pool = Pool(processes=2)             
p1 = pool.apply_async(foo)
p1 = pool.apply_async(bar)

Les questions que j'ai pour cette implémentation seraient: 1) Ai-je besoin d'un pool.close (), pool.join ()? 2) Est-ce que pool.map () les rendrait tous complets avant que je puisse obtenir des résultats? Et si c'est le cas, sont-ils toujours asynchrones? 3) En quoi pool.apply_async () différerait-il de chaque processus avec pool.apply () 4) En quoi cela différerait-il de l'implémentation précédente avec Process?

37
dman

Les deux scénarios que vous avez énumérés accomplissent la même chose mais de manières légèrement différentes.

Le premier scénario démarre deux processus distincts (appelez-les P1 et P2) et démarre P1 exécutant foo et P2 exécutant bar, puis attend que les deux processus aient terminé leurs tâches respectives.

Le deuxième scénario démarre deux processus (appelez-les Q1 et Q2) et démarre d'abord foo sur Q1 ou Q2, puis démarre bar sur Q1 ou Q2. Le code attend ensuite le retour des deux appels de fonction.

Ainsi, le résultat net est en fait le même, mais dans le premier cas, vous êtes assuré d'exécuter foo et bar sur des processus différents.

En ce qui concerne les questions spécifiques que vous vous posiez sur la concurrence, la méthode .join() sur un Process se bloque en effet jusqu'à la fin du processus, mais parce que vous avez appelé .start() sur les deux P1 et P2 (dans votre premier scénario) avant de rejoindre, les deux processus s'exécuteront de manière asynchrone. L'interprète attendra cependant que P1 se termine avant d'essayer d'attendre que P2 se termine.

Pour vos questions sur le scénario de pool, vous devez techniquement utiliser pool.close() mais cela dépend en quelque sorte de ce dont vous pourriez avoir besoin par la suite (s'il sort juste du cadre, vous n'avez pas besoin de le fermer nécessairement ). pool.map() est un type d'animal complètement différent, car il distribue un tas d'arguments à la même fonction (de manière asynchrone), à ​​travers les processus du pool, puis attend jusqu'à ce que tous les appels de fonction soient terminés avant de renvoyer la liste des résultats .

30
lmjohns3

Puisque vous récupérez des données à partir d'appels curl, vous êtes lié aux E/S. Dans ce cas grequests pourrait être utile. Ce ne sont vraiment ni des processus ni des threads mais des coroutines - des threads légers. Cela vous permettrait d'envoyer des requêtes HTTP asynchrones, puis d'utiliser multiprocessing.Pool Pour accélérer la partie liée au processeur.

1) Puisque les blocs de jointure jusqu'à la fin du processus d'appel ... cela signifie-t-il que le processus p1 doit se terminer avant le démarrage du processus p2?

Oui, p2.join() est appelée après que p1.join() a renvoyé ce qui signifie que p1 Est terminé.

1) Ai-je besoin d'un pool.close (), pool.join ()

Vous pourriez vous retrouver avec des processus orphelins sans faire close() et join() (si les processus servent indéfiniment)

2) Est-ce que pool.map () les rendrait tous complets avant que je puisse obtenir des résultats? Et si c'est le cas, sont-ils toujours asynchrones?

Ils sont exécutés de manière asynchrone, mais la map() est bloquée jusqu'à ce que toutes les tâches soient terminées.

3) En quoi pool.apply_async () serait-il différent de chaque processus avec pool.apply ()

pool.apply() est bloquant, donc en gros vous feriez le traitement de manière synchrone.

4) En quoi cela différerait-il de la mise en œuvre précédente avec Process

Les chances sont qu'un travailleur soit fait avec foo avant d'appliquer bar donc vous pourriez vous retrouver avec un seul travailleur faisant tout le travail. De plus, si l'un de vos employés décède, Pool en génère automatiquement un nouveau (vous devrez réappliquer la tâche).

Pour résumer : je préfère aller avec Pool - il est parfait pour les cas producteurs-consommateurs et s'occupe de toute la distribution des tâches logique.

11
Maciej Gol