Per me questo è in realtà piuttosto semplice:
L' opzione sottoprocesso :
subprocess
è per eseguire altri eseguibili --- è fondamentalmente un wrapper intorno os.fork()
e os.execve()
con un po 'di supporto per l'impianto idraulico opzionale (impostazione di PIPE da e verso i sottoprocessi. Ovviamente potresti altri meccanismi di comunicazione tra processi (IPC), come socket, o Posix o SysV ha condiviso la memoria, ma sarai limitato a qualsiasi interfaccia e canale IPC supportati dai programmi che stai chiamando.
Comunemente, uno usa qualsiasi in modo subprocess
sincrono --- semplicemente chiamando qualche utility esterna e rileggendo il suo output o aspettando il suo completamento (magari leggendo i suoi risultati da un file temporaneo, o dopo averli inviati a qualche database).
Tuttavia si possono generare centinaia di sottoprocessi e interrogarli. La mia classe di utilità preferita personale fa esattamente questo.
Il più grande svantaggio del subprocess
modulo è che il supporto I / O è generalmente bloccante. C'è una bozza di PEP-3145 per risolverlo in qualche futura versione di Python 3.x e un asyncproc alternativo (Attenzione che porta direttamente al download, non a nessun tipo di documentazione né README). Ho anche scoperto che è relativamente facile importare fcntl
e manipolare Popen
direttamente i descrittori di file PIPE, anche se non so se è portabile su piattaforme non UNIX.
(Aggiornamento: 7 agosto 2019: supporto Python 3 per sottoprocessi ayncio : sottoprocessi asyncio )
subprocess
non ha quasi alcun supporto per la gestione degli eventi ... anche se puoi usare il signal
modulo e semplici segnali UNIX / Linux della vecchia scuola --- uccidendo i tuoi processi dolcemente, per così dire.
L' opzione multiprocessing :
multiprocessing
serve per eseguire funzioni all'interno del codice esistente (Python) con supporto per comunicazioni più flessibili tra questa famiglia di processi. In particolare, è meglio costruire il tuo multiprocessing
IPC attorno agli Queue
oggetti del modulo, ove possibile, ma puoi anche utilizzare Event
oggetti e varie altre funzionalità (alcune delle quali, presumibilmente, sono costruite attorno al mmap
supporto sulle piattaforme in cui tale supporto è sufficiente).
Il multiprocessing
modulo di Python ha lo scopo di fornire interfacce e funzionalità molto simili a quelle che threading
consentono a CPython di ridimensionare l'elaborazione tra più CPU / core nonostante il GIL (Global Interpreter Lock). Sfrutta tutto l'impegno di blocco e coerenza SMP a grana fine svolto dagli sviluppatori del kernel del sistema operativo.
L' opzione di filettatura :
threading
è per una gamma piuttosto ristretta di applicazioni che sono vincolate a I / O (non è necessario scalare su più core della CPU) e che beneficiano della latenza estremamente bassa e dell'overhead di commutazione della commutazione dei thread (con memoria core condivisa) rispetto al processo / cambio di contesto. Su Linux questo è quasi il set vuoto (i tempi di commutazione del processo Linux sono estremamente vicini ai suoi thread-switch).
threading
soffre di due principali svantaggi in Python .
Uno, ovviamente, è l'implementazione specifica, che interessa principalmente CPython. Questo è il GIL. Per la maggior parte, la maggior parte dei programmi CPython non trarrà vantaggio dalla disponibilità di più di due CPU (core) e spesso le prestazioni risentiranno della contesa di blocco GIL.
Il problema più grande che non è specifico dell'implementazione, è che i thread condividono la stessa memoria, gestori di segnali, descrittori di file e alcune altre risorse del sistema operativo. Quindi il programmatore deve essere estremamente attento al blocco degli oggetti, alla gestione delle eccezioni e ad altri aspetti del loro codice che sono entrambi sottili e che possono uccidere, bloccare o bloccare l'intero processo (suite di thread).
In confronto, il multiprocessing
modello fornisce a ciascun processo la propria memoria, descrittori di file, ecc. Un arresto anomalo o un'eccezione non gestita in uno qualsiasi di essi ucciderà solo quella risorsa e gestire in modo robusto la scomparsa di un processo figlio o fratello può essere considerevolmente più semplice del debugging, dell'isolamento e risolvere o aggirare problemi simili nei thread.
- (Nota: l'uso di
threading
con i principali sistemi Python, come NumPy , può soffrire notevolmente meno della contesa GIL rispetto alla maggior parte del tuo codice Python. Questo perché sono stati specificamente progettati per farlo; le parti native / binarie di NumPy, ad esempio, rilascerà il GIL quando è sicuro).
L' opzione contorta :
Vale anche la pena notare che Twisted offre un'altra alternativa elegante e allo stesso tempo molto difficile da capire . Fondamentalmente, con il rischio di semplificare eccessivamente al punto in cui i fan di Twisted potrebbero prendere d'assalto la mia casa con forconi e torce, Twisted fornisce il multi-tasking cooperativo guidato dagli eventi all'interno di qualsiasi (singolo) processo.
Per capire come ciò sia possibile, si dovrebbe leggere le caratteristiche di select()
(che possono essere costruite attorno a select () o poll () o simili chiamate di sistema OS). Fondamentalmente è tutto guidato dalla possibilità di fare una richiesta al sistema operativo di dormire in attesa di qualsiasi attività su un elenco di descrittori di file o di un timeout.
Il risveglio da ciascuna di queste chiamate a select()
è un evento --- uno che coinvolge input disponibile (leggibile) su un certo numero di socket o descrittori di file, o lo spazio di buffer diventa disponibile su altri descrittori o socket (scrivibili), alcune condizioni eccezionali (TCP pacchetti PUSH fuori banda, ad esempio) o un TIMEOUT.
Pertanto, il modello di programmazione Twisted è costruito intorno alla gestione di questi eventi e quindi al looping sul gestore "principale" risultante, permettendogli di inviare gli eventi ai gestori.
Personalmente penso al nome, Twisted come evocativo del modello di programmazione ... poiché il tuo approccio al problema deve essere, in un certo senso, "contorto" al rovescio. Piuttosto che concepire il tuo programma come una serie di operazioni su dati di input e output o risultati, stai scrivendo il tuo programma come servizio o daemon e definisci come reagisce a vari eventi. (In effetti il nucleo "ciclo principale" di un programma Twisted è (di solito? Sempre?) A reactor()
).
Le sfide principali nell'utilizzo di Twisted riguardano il distogliere la mente dal modello basato sugli eventi e anche evitare l'uso di librerie di classi o toolkit che non sono scritti per cooperare all'interno del framework Twisted. Questo è il motivo per cui Twisted fornisce i propri moduli per la gestione del protocollo SSH, per curses e le proprie funzioni di sottoprocesso / Popen, e molti altri moduli e gestori di protocollo che, a prima vista, sembrerebbero duplicare le cose nelle librerie standard Python.
Penso che sia utile capire Twisted a livello concettuale anche se non si intende mai utilizzarlo. Può fornire informazioni sulle prestazioni, i conflitti e la gestione degli eventi nel threading, nel multiprocessing e persino nella gestione di sottoprocessi, nonché in qualsiasi elaborazione distribuita intrapresa.
( Nota: le versioni più recenti di Python 3.x includono funzionalità asyncio (asynchronous I / O) come async def , il decoratore @ async.coroutine e la parola chiave await , e sono rese disponibili dal supporto futuro . Tutte queste sono più o meno simili a Twisted from a process (co-operative multitasking) prospettiva). (Per lo stato attuale del supporto Twisted per Python 3, controlla: https://twistedmatrix.com/documents/current/core/howto/python3.html )
L' opzione distribuita :
Ancora un altro ambito dell'elaborazione di cui non hai chiesto, ma che vale la pena considerare, è quello dell'elaborazione distribuita . Esistono molti strumenti e framework Python per l'elaborazione distribuita e il calcolo parallelo. Personalmente penso che il più facile da usare sia quello che viene considerato meno spesso in quello spazio.
È quasi banale creare un'elaborazione distribuita attorno a Redis . L'intero archivio chiavi può essere utilizzato per memorizzare unità di lavoro e risultati, le LISTE Redis possono essere utilizzate Queue()
come oggetti simili e il supporto PUB / SUB può essere utilizzato per la Event
gestione simile. È possibile eseguire l'hashing delle chiavi e utilizzare i valori, replicati su un cluster libero di istanze Redis, per archiviare la topologia e le mappature hash-token per fornire hash e failover coerenti per il ridimensionamento oltre la capacità di ogni singola istanza per il coordinamento dei lavoratori e dati di marshalling (pickled, JSON, BSON o YAML) tra di loro.
Ovviamente quando inizi a costruire una soluzione su scala più ampia e più sofisticata attorno a Redis, stai reimplementando molte delle funzionalità che sono già state risolte utilizzando, Celery , Apache Spark e Hadoop , Zookeeper , ecc. , Cassandra e così via. Tutti hanno moduli per l'accesso Python ai loro servizi.
[Aggiornamento: un paio di risorse da prendere in considerazione se stai prendendo in considerazione Python per un uso intensivo di calcolo su sistemi distribuiti: IPython Parallel e PySpark . Sebbene si tratti di sistemi informatici distribuiti di uso generale, sono sottosistemi particolarmente accessibili e diffusi di data science e analytics].
Conclusione
Ecco la gamma di alternative di elaborazione per Python, da thread singolo, con semplici chiamate sincrone a sottoprocessi, pool di sottoprocessi con polling, thread e multiprocessing, multi-tasking cooperativo basato su eventi e elaborazione distribuita.