Concurrent.futures vs multitraitement dans Python 3

148

Python 3.2 a introduit les contrats à terme simultanés , qui semble être une combinaison avancée des anciens modules de thread et de multitraitement .

Quels sont les avantages et les inconvénients de son utilisation pour les tâches liées au processeur par rapport à l'ancien module de multitraitement?

Cet article suggère qu'ils sont beaucoup plus faciles à utiliser - est-ce le cas?

GIS-Jonathan
la source

Réponses:

145

Je n'appellerais pas concurrent.futuresplus «avancé» - c'est une interface plus simple qui fonctionne à peu près de la même manière, que vous utilisiez plusieurs threads ou plusieurs processus comme gadget de parallélisation sous-jacent.

Ainsi, comme pratiquement toutes les instances d '«interface plus simple», les mêmes compromis sont à peu près impliqués: il a une courbe d'apprentissage moins profonde, en grande partie simplement parce qu'il y a tellement moins de choses à apprendre; mais, comme il offre moins d'options, il peut éventuellement vous frustrer d'une manière que les interfaces plus riches ne le feront pas.

En ce qui concerne les tâches liées au processeur, c'est beaucoup trop sous-spécifié pour dire beaucoup de sens. Pour les tâches liées au processeur sous CPython, vous avez besoin de plusieurs processus plutôt que de plusieurs threads pour avoir une chance d'obtenir une accélération. Mais l'ampleur (le cas échéant) de l'accélération que vous obtenez dépend des détails de votre matériel, de votre système d'exploitation et en particulier de la quantité de communication inter-processus requise par vos tâches spécifiques. Sous les couvertures, tous les gadgets de parallélisation inter-processus reposent sur les mêmes primitives de système d'exploitation - l'API de haut niveau que vous utilisez pour y parvenir n'est pas un facteur principal de la vitesse du résultat.

Edit: exemple

Voici le code final affiché dans l'article que vous avez référencé, mais j'ajoute une instruction d'importation nécessaire pour le faire fonctionner:

from concurrent.futures import ProcessPoolExecutor
def pool_factorizer_map(nums, nprocs):
    # Let the executor divide the work among processes by using 'map'.
    with ProcessPoolExecutor(max_workers=nprocs) as executor:
        return {num:factors for num, factors in
                                zip(nums,
                                    executor.map(factorize_naive, nums))}

Voici exactement la même chose en utilisant à la multiprocessingplace:

import multiprocessing as mp
def mp_factorizer_map(nums, nprocs):
    with mp.Pool(nprocs) as pool:
        return {num:factors for num, factors in
                                zip(nums,
                                    pool.map(factorize_naive, nums))}

Notez que la possibilité d'utiliser des multiprocessing.Poolobjets comme gestionnaires de contexte a été ajoutée dans Python 3.3.

Lequel est le plus facile à utiliser? LOL ;-) Ils sont essentiellement identiques.

Une différence est qu'il Poolprend en charge tellement de façons différentes de faire les choses que vous ne réaliserez peut- être pas à quel point cela peut être facile tant que vous n'avez pas gravi un chemin dans la courbe d'apprentissage.

Encore une fois, toutes ces différentes manières sont à la fois une force et une faiblesse. Ils sont une force car la flexibilité peut être requise dans certaines situations. Ils sont une faiblesse car "de préférence une seule façon évidente de le faire". Un projet collé exclusivement (si possible) à concurrent.futuressera probablement plus facile à maintenir à long terme, en raison du manque de nouveauté gratuite dans la façon dont son API minimale peut être utilisée.

Tim Peters
la source
20
"vous avez besoin de plusieurs processus plutôt que de plusieurs threads pour avoir une chance d'obtenir une accélération" est trop sévère. Si la vitesse est importante; le code peut déjà utiliser une bibliothèque C et donc il peut publier GIL par exemple, regex, lxml, numpy.
jfs
4
@JFSebastian, merci d'avoir ajouté cela - peut-être aurais-je dû dire "sous pur CPython", mais j'ai bien peur qu'il n'y ait pas de moyen rapide d'expliquer la vérité ici sans discuter du GIL.
Tim Peters
2
Et il convient de mentionner que les threads peuvent être particulièrement utiles et suffisants lorsqu'ils fonctionnent avec de longues E / S.
kotrfa
9
@TimPeters A certains égards, a ProcessPoolExecutoren fait plus d'options que Poolcar ProcessPoolExecutor.submitrenvoie des Futureinstances qui autorisent l'annulation ( cancel), vérifie quelle exception a été levée ( exception) et ajoute dynamiquement un rappel à appeler à la fin ( add_done_callback). Aucune de ces fonctionnalités n'est disponible avec les AsyncResultinstances renvoyées par Pool.apply_async. Dans d' autres moyens Poola plus d' options en raison de initializer/ initargs, maxtasksperchildet contextdans Pool.__init__, et plus les méthodes exposées par Poolexemple.
max
2
@max, bien sûr, mais notez que la question ne concernait pas Pool, mais les modules. Poolest une petite partie de ce qui multiprocessingest dedans, et est si loin dans la documentation qu'il faut un certain temps pour que les gens se rendent compte que cela existe même multiprocessing. Cette réponse particulière s'est concentrée sur Poolparce que c'est tout l'article utilisé par l'OP et qui cfest «beaucoup plus facile à travailler» n'est tout simplement pas vrai sur ce que l'article a discuté. Au - delà, cfest as_completed()peut aussi être très utile.
Tim Peters