Come funzionano i thread in Python e quali sono i comuni problemi specifici del thread Python?


85

Ho cercato di capire come funzionano i thread in Python ed è difficile trovare buone informazioni su come funzionano. Potrei semplicemente perdere un collegamento o qualcosa del genere, ma sembra che la documentazione ufficiale non sia molto approfondita sull'argomento e non sono stato in grado di trovare un buon articolo.

Da quello che posso dire, solo un thread può essere eseguito in una volta e il thread attivo cambia ogni 10 istruzioni o giù di lì?

Dove c'è una buona spiegazione o puoi fornirne una? Sarebbe anche molto bello essere consapevoli dei problemi comuni che si incontrano durante l'utilizzo di thread con Python.

Risposte:


51

Sì, grazie al Global Interpreter Lock (GIL) è possibile eseguire un solo thread alla volta. Ecco alcuni link con alcuni approfondimenti su questo:

Dall'ultimo link una citazione interessante:

Lascia che ti spieghi cosa significa tutto ciò. I thread vengono eseguiti all'interno della stessa macchina virtuale e quindi vengono eseguiti sulla stessa macchina fisica. I processi possono essere eseguiti sulla stessa macchina fisica o su un'altra macchina fisica. Se si progetta la propria applicazione attorno ai thread, non si è fatto nulla per accedere a più macchine. Quindi, puoi scalare fino a quanti core ci sono sulla singola macchina (che sarà un bel po 'nel tempo), ma per raggiungere davvero le scale web, dovrai comunque risolvere il problema con più macchine.

Se si desidera utilizzare il multi core, il pyprocessing definisce un'API basata sul processo per eseguire una vera parallelizzazione. Il PEP include anche alcuni benchmark interessanti.


1
Davvero un commento alla citazione smoothspan: sicuramente il threading Python ti limita effettivamente a un core, anche se la macchina ne ha diversi? Potrebbero esserci vantaggi dal multicore poiché il thread successivo può essere pronto per l'uso senza un cambio di contesto, ma i thread Python non possono mai utilizzare> 1 core alla volta.
James Brady,

2
Corretto, i thread Python sono praticamente limitati all'unico core, A MENO CHE un modulo C interagisca bene con il GIL e esegua il proprio thread nativo.
Arafangion

In realtà, più core rendono i thread meno efficienti poiché c'è molto churn con il controllo se ogni thread può accedere al GIL. Anche con il nuovo GIL, le prestazioni sono ancora peggiori ... dabeaz.com/python/NewGIL.pdf
Basic

2
Si prega di notare che le considerazioni GIL non si applicano a tutti gli interpreti. Per quanto ne so, sia IronPython che Jython funzionano senza un GIL, consentendo al loro codice di fare un uso più efficace dell'hardware multiprocessore. Come accennato da Arafangion, l'interprete CPython può anche essere eseguito correttamente in multi-thread se il codice che non necessita dell'accesso agli elementi di dati Python rilascia il blocco, quindi lo acquisisce di nuovo prima di tornare.
holdenweb

Cosa causa un cambio di contesto tra i thread in Python? È basato sugli interrupt del timer? Blocco o richiesta di rendimento specifica?
CMCDragonkai

36

Python è un linguaggio abbastanza facile da inserire, ma ci sono avvertenze. La cosa più importante che devi sapere è il Global Interpreter Lock. Ciò consente a un solo thread di accedere all'interprete. Ciò significa due cose: 1) ti ritrovi raramente a usare un'istruzione lock in python e 2) se vuoi sfruttare i sistemi multiprocessore, devi usare processi separati. EDIT: dovrei anche sottolineare che puoi mettere parte del codice in C / C ++ se vuoi aggirare anche il GIL.

Pertanto, è necessario riconsiderare il motivo per cui si desidera utilizzare i thread. Se vuoi parallelizzare la tua app per sfruttare l'architettura dual-core, devi considerare di suddividere la tua app in più processi.

Se vuoi migliorare la reattività, dovresti CONSIDERARE l'utilizzo di thread. Ci sono però altre alternative, vale a dire il microthreading . Ci sono anche alcuni framework che dovresti esaminare:


@JS - Risolto. Quella lista era comunque obsoleta.
Jason Baker,

Mi sembra solo sbagliato che tu abbia bisogno di più processi, con tutto il sovraccarico che ciò comporta, per sfruttare un sistema multi-core. Abbiamo alcuni server con 32 core logici, quindi ho bisogno di 32 processi per usarli in modo efficiente? Madness
Basic

@Basic - L'overhead nell'avvio di un processo rispetto all'avvio di un thread in questi giorni è minimo. Suppongo che potresti iniziare a vedere problemi se parliamo di migliaia di query al secondo, ma in primo luogo metterei in dubbio la scelta di Python per un servizio così impegnato.
Jason Baker

20

Di seguito è riportato un esempio di threading di base. Verranno generati 20 thread; ogni thread produrrà il suo numero di thread. Eseguilo e osserva l'ordine in cui stampano.

import threading
class Foo (threading.Thread):
    def __init__(self,x):
        self.__x = x
        threading.Thread.__init__(self)
    def run (self):
          print str(self.__x)

for x in xrange(20):
    Foo(x).start()

Come hai accennato, i thread Python sono implementati tramite il time-slicing. È così che ottengono l'effetto "parallelo".

Nel mio esempio la mia classe Foo estende il thread, quindi implemento il runmetodo, che è dove va il codice che vorresti eseguire in un thread. Per avviare il thread si chiama start()sull'oggetto thread, che richiamerà automaticamente il filerun metodo ...

Naturalmente, queste sono solo le basi. Alla fine vorrai imparare a conoscere semafori, mutex e blocchi per la sincronizzazione dei thread e il passaggio dei messaggi.


10

Usa i thread in Python se i singoli lavoratori stanno eseguendo operazioni di I / O. Se stai cercando di scalare su più core su una macchina, trova un buon framework IPC per Python o scegli un linguaggio diverso.


6

Nota: ovunque io menzioni threadintendo specificamente i thread in Python fino a quando non viene dichiarato esplicitamente.

I thread funzionano in modo leggermente diverso in Python se provieni dallo C/C++sfondo. In python, solo un thread può essere in esecuzione in un dato momento. Ciò significa che i thread in python non possono davvero sfruttare la potenza di più core di elaborazione poiché per progettazione non è possibile che i thread vengano eseguiti parallelamente su più core.

Poiché la gestione della memoria in python non è thread-safe, ogni thread richiede un accesso esclusivo alle strutture dati nell'interprete python.Questo accesso esclusivo viene acquisito da un meccanismo chiamato (global interpretr lock) .GIL

Why does python use GIL?

Per evitare che più thread accedano allo stato dell'interprete contemporaneamente e danneggino lo stato dell'interprete.

L'idea è che ogni volta che un thread viene eseguito (anche se è il thread principale) , viene acquisito un GIL e dopo un intervallo di tempo predefinito il GIL viene rilasciato dal thread corrente e riacquistato da qualche altro thread (se presente).

Why not simply remove GIL?

Non è che sia impossibile rimuovere GIL, è solo che nel processo di farlo finiamo per mettere più blocchi all'interno dell'interprete per serializzare l'accesso, il che rende meno performante anche una singola applicazione con thread.

quindi il costo della rimozione di GIL viene ripagato dalla riduzione delle prestazioni di una singola applicazione thread, che non è mai desiderata.

So when does thread switching occurs in python?

Il cambio di thread si verifica quando viene rilasciato GIL. Quindi, quando viene rilasciato GIL? Ci sono due scenari da prendere in considerazione.

Se un thread sta eseguendo operazioni di CPU Bound (elaborazione immagine Ex).

Nelle versioni precedenti di python, il cambio di thread si verificava dopo un numero fisso di istruzioni python ed era impostato di default su 100. Si è scoperto che non è una buona politica per decidere quando il passaggio dovrebbe avvenire dato il tempo impiegato per eseguire una singola istruzione può molto selvaggiamente da millisecondi a anche un secondo. Pertanto, il rilascio di GIL dopo ogni 100istruzione indipendentemente dal tempo necessario per l'esecuzione è una cattiva politica.

Nelle nuove versioni invece di utilizzare il conteggio delle istruzioni come metrica per cambiare thread, viene utilizzato un intervallo di tempo configurabile. L'intervallo di commutazione predefinito è 5 millisecondi. È possibile ottenere l'intervallo di commutazione corrente utilizzando sys.getswitchinterval(). Questo può essere modificato usandosys.setswitchinterval()

Se un thread sta eseguendo alcune operazioni di I / O (accesso al file system Ex o I /
O di rete)

GIL viene rilasciato ogni volta che il thread attende il completamento di un'operazione di I / O.

Which thread to switch to next?

L'interprete non ha un proprio scheduler. Il thread che viene pianificato alla fine dell'intervallo è una decisione del sistema operativo. .


3

Una semplice soluzione al GIL è il modulo multiprocessing . Può essere utilizzato in sostituzione del modulo threading ma utilizza più processi interprete invece dei thread. Per questo motivo c'è un po 'più di overhead rispetto al threading semplice per cose semplici, ma ti dà il vantaggio di una vera parallelizzazione se ne hai bisogno. Inoltre, si adatta facilmente a più macchine fisiche.

Se hai bisogno di una parallelizzazione su larga scala di quello che guarderei oltre, ma se vuoi solo scalare a tutti i core di un computer o di pochi diversi senza tutto il lavoro che sarebbe necessario per implementare un framework più completo, questo è per te .


2

Cerca di ricordare che il GIL è impostato per eseguire sondaggi ogni tanto per mostrare l'aspetto di più attività. Questa impostazione può essere ottimizzata, ma offro il suggerimento che dovrebbe esserci del lavoro che i thread stanno facendo o che molti cambi di contesto causeranno problemi.

Mi spingerei al punto di suggerire più genitori sui processori e cercare di mantenere lavori simili sugli stessi core.

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.