Concurrent.futures vs Multiprocessing in Python 3


Risposte:


145

Non definirei concurrent.futurespiù "avanzato": è un'interfaccia più semplice che funziona allo stesso modo indipendentemente dal fatto che si utilizzino più thread o più processi come espediente di parallelizzazione sottostante.

Così, come quasi tutte le istanze di "un'interfaccia più semplice", più o meno le stesse trade-off sono coinvolti: ha una curva di apprendimento meno profonda, in gran parte solo perché c'è così tanto meno disponibili ad imparare; ma, poiché offre un minor numero di opzioni, alla fine potrebbe frustrarti come non faranno le interfacce più ricche.

Per quanto riguarda le attività legate alla CPU, è troppo poco specificato per dire molto significativo. Per le attività associate alla CPU in CPython, sono necessari più processi anziché più thread per avere la possibilità di ottenere uno speedup. Ma la quantità (se presente) di una velocità che ottieni dipende dai dettagli del tuo hardware, del tuo sistema operativo e in particolare dalla quantità di comunicazione tra processi richiesta dalle tue attività specifiche. Sotto le copertine, tutti i trucchi di parallelizzazione tra processi si basano sulle stesse primitive del sistema operativo: l'API di alto livello che usi per ottenere a quelli non è un fattore primario nella velocità di fondo.

Modifica: esempio

Ecco il codice finale mostrato nell'articolo a cui hai fatto riferimento, ma sto aggiungendo una dichiarazione di importazione necessaria per farlo funzionare:

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))}

Ecco esattamente la stessa cosa usando multiprocessinginvece:

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))}

Si noti che la possibilità di utilizzare gli multiprocessing.Poologgetti come gestori di contesto è stata aggiunta in Python 3.3.

Con quale è più facile lavorare? LOL ;-) Sono sostanzialmente identici.

Una differenza è che Poolsupporta così tanti modi diversi di fare le cose che potresti non renderti conto di quanto possa essere facile fino a quando non hai scalato abbastanza in alto la curva di apprendimento.

Ancora una volta, tutti questi modi diversi sono sia una forza che una debolezza. Sono un punto di forza perché la flessibilità può essere richiesta in alcune situazioni. Sono una debolezza a causa di "preferibilmente solo un modo ovvio per farlo". Un progetto basato esclusivamente (se possibile) su concurrent.futuressarà probabilmente più facile da mantenere a lungo termine, a causa della mancanza di novità gratuite nel modo in cui può essere utilizzata la sua API minima.


20
"hai bisogno di più processi anziché più thread per avere qualche possibilità di ottenere uno speedup" è troppo duro. Se la velocità è importante; il codice potrebbe già utilizzare una libreria C e quindi può rilasciare GIL, ad esempio regex, lxml, numpy.
jfs,

4
@JFSebastian, grazie per averlo aggiunto - forse avrei dovuto dire "sotto puro CPython", ma temo non ci sia modo di spiegare la verità qui senza discutere del GIL.
Tim Peters,

2
E vale la pena ricordare che i thread potrebbero essere particolarmente utili e sufficienti quando si opera con un IO lungo.
Kotrfa,

9
@TimPeters In qualche modo ProcessPoolExecutorha effettivamente più opzioni rispetto al Poolfatto che ProcessPoolExecutor.submitrestituisce Futureistanze che consentono la cancellazione ( cancel), controllando quale eccezione è stata sollevata ( exception) e aggiungendo dinamicamente un callback da chiamare al completamento ( add_done_callback). Nessuna di queste funzionalità è disponibile con AsyncResultistanze restituite da Pool.apply_async. In altri modi Poolha più opzioni a causa di initializer/ initargs, maxtasksperchilde contextin Pool.__init__, e più metodi esposti da Poolesempio.
max

2
@max, certo, ma nota che la domanda non Poolriguardava i moduli. Poolè una piccola parte di ciò che è dentro multiprocessing, ed è così in fondo ai documenti che ci vuole un po 'prima che le persone realizzino che esiste multiprocessing. Questa risposta particolare si è concentrata Poolperché è tutto l'articolo a cui l'OP si è collegato e che cfè "molto più facile da lavorare" semplicemente non è vero su ciò di cui l'articolo ha discusso. Oltre a ciò, cf's as_completed()può anche essere molto utile.
Tim Peters,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.