Cos'è il blocco dell'interprete globale (GIL) in CPython?


244

Che cos'è un blocco dell'interprete globale e perché è un problema?

È stato fatto molto rumore rimuovendo il GIL da Python e mi piacerebbe capire perché sia ​​così importante. Non ho mai scritto un compilatore né un interprete, quindi non essere frugale con i dettagli, probabilmente avrò bisogno che capiscano.


3
Guarda David Beazley raccontarti tutto ciò che avresti sempre voluto sapere sul GIL.
hughdbrown,

1
Ecco un lungo articolo che parla del GIL e del threading in Python che ho scritto qualche tempo fa. Ci sono molti
jnoller

Ecco alcuni codici che dimostrano gli effetti di GIL: github.com/cankav/python_gil_demonstration
Can Kavaklıoğlu

3
Trovo che questa sia la migliore spiegazione di GIL. Si prega di leggere. dabeaz.com/python/UnderstandingGIL.pdf
suhao399

Risposte:


220

Il GIL di Python ha lo scopo di serializzare l'accesso agli interpreti interni da thread diversi. Su sistemi multi-core, significa che più thread non possono effettivamente utilizzare più core. (Se il GIL non ha portato a questo problema, la maggior parte delle persone non si preoccuperebbe del GIL: viene sollevato solo come un problema a causa della crescente prevalenza di sistemi multi-core.) Se vuoi capirlo in dettaglio, puoi vedere questo video o guardare questo set di diapositive . Potrebbe essere troppe informazioni, ma poi hai chiesto i dettagli :-)

Si noti che GIL di Python è davvero solo un problema per CPython, l'implementazione di riferimento. Jython e IronPython non hanno un GIL. Come sviluppatore Python, generalmente non ti imbatti in GIL a meno che non stia scrivendo un'estensione C. Gli autori di estensioni C devono rilasciare GIL quando le loro estensioni bloccano l'I / O, in modo che altri thread nel processo Python abbiano la possibilità di essere eseguiti.


46
Buona risposta - sostanzialmente significa che i thread in Python sono buoni solo per bloccare l'I / O; la tua app non supererà mai 1 core della CPU nell'uso del processore
Ana Betts,

8
"Come sviluppatore Python, in genere non ti imbatti in GIL a meno che non stia scrivendo un'estensione C" - Potresti non sapere che la causa del tuo codice multi-thread in esecuzione a un ritmo di lumache è il GIL, ma tu ' Ne sentirò sicuramente gli effetti. Mi stupisce ancora che sfruttare un server a 32 core con Python significhi che ho bisogno di 32 processi con tutti i costi associati.
Base

6
@PaulBetts: non è vero. È probabile che il codice critico per le prestazioni utilizzi già estensioni C che possono rilasciare moduli GIL, ad es regex. lxml, numpyModuli. Cython consente di rilasciare GIL nel codice personalizzato, ad es.b2a_bin(data)
jfs

5
@Paul Betts: è possibile ottenere oltre 1 codice CPU dell'utilizzo del processore utilizzando il modulo multiprocessore . La creazione di più processi è "più pesante" rispetto alla creazione di più thread, ma se hai davvero bisogno di lavorare in parallelo, in Python, è un'opzione.
AJNeufeld,

1
@david_adler Sì, ancora, e probabilmente rimarrà tale ancora per un po '. Ciò non ha davvero impedito a Python di essere davvero utile per molti carichi di lavoro diversi.
Vinay Sajip,

59

Supponiamo di avere più thread che non toccano realmente i dati degli altri. Quelli dovrebbero essere eseguiti nel modo più indipendente possibile. Se si dispone di un "blocco globale" che è necessario acquisire per (dire) una funzione, ciò può finire come un collo di bottiglia. Puoi finire per non trarre grandi benefici dall'avere più thread in primo luogo.

Per inserirlo in un'analogia del mondo reale: immagina 100 sviluppatori che lavorano in un'azienda con una sola tazza di caffè. La maggior parte degli sviluppatori passerebbe il tempo ad aspettare il caffè invece di scrivere codice.

Niente di tutto questo è specifico di Python - in primo luogo non conosco i dettagli di ciò di cui Python necessitava un GIL. Tuttavia, si spera che ti abbia dato un'idea migliore del concetto generale.


Tranne il fatto che aspettare la tazza di caffè sembra un processo I / O abbastanza legato, poiché possono sicuramente fare altre cose mentre aspettano la tazza. GIL ha un effetto molto limitato sui thread pesanti I / O che trascorrono comunque gran parte del loro tempo ad aspettare.
Cruncher,


36

Per prima cosa comprendiamo cosa fornisce GIL di Python:

Qualsiasi operazione / istruzione viene eseguita nell'interprete. GIL assicura che l'interprete sia trattenuto da un singolo thread in un determinato istante di tempo . E il tuo programma Python con più thread funziona in un singolo interprete. In ogni particolare istante di tempo, questo interprete è tenuto da un singolo thread. Significa che solo il thread che contiene l'interprete è in esecuzione in qualsiasi momento .

Ora, perché è un problema:

La tua macchina potrebbe avere più core / processori. E più core consentono l'esecuzione simultanea di più thread, ovvero più thread potrebbero essere eseguiti in qualsiasi particolare istante di tempo. . Ma poiché l'interprete è tenuto da un singolo thread, altri thread non stanno facendo nulla anche se hanno accesso a un core. Quindi, non stai ottenendo alcun vantaggio fornito da più core perché in ogni istante viene utilizzato solo un singolo core, che è il core utilizzato dal thread che detiene attualmente l'interprete. Pertanto, l'esecuzione del programma richiederà tanto tempo come se fosse un singolo programma thread.

Tuttavia, operazioni potenzialmente bloccanti o di lunga durata, come I / O, elaborazione delle immagini e crunching dei numeri NumPy, avvengono al di fuori di GIL. Tratto da qui . Quindi, per tali operazioni, un'operazione multithread sarà ancora più veloce di una singola operazione thread, nonostante la presenza di GIL. Quindi, GIL non è sempre un collo di bottiglia.

Modifica: GIL è un dettaglio di implementazione di CPython. IronPython e Jython non hanno GIL, quindi un programma veramente multithread dovrebbe essere possibile in essi, pensato che non avessi mai usato PyPy e Jython e non ne sono sicuro.


4
Nota : PyPy ha il GIL . Riferimento : http://doc.pypy.org/en/latest/faq.html#does-pypy-have-a-gil-why . Mentre Ironpython e Jython non hanno il GIL.
Tasdik Rahman,

In effetti, PyPy ha un GIL, ma IronPython no.
Emmanuel,

@Emmanuel Modificata la risposta per rimuovere PyPy e includere IronPython.
Akshar Raaj,

17

Python non consente il multi-threading nel vero senso della parola. Ha un pacchetto multi-thread ma se vuoi multi-thread per velocizzare il tuo codice, di solito non è una buona idea usarlo. Python ha un costrutto chiamato Global Interpreter Lock (GIL).

https://www.youtube.com/watch?v=ph374fJqFPE

GIL si assicura che solo uno dei tuoi "thread" possa essere eseguito contemporaneamente. Un thread acquisisce il GIL, fa un piccolo lavoro, quindi passa il GIL al thread successivo. Questo accade molto rapidamente, quindi all'occhio umano può sembrare che i tuoi thread si stiano eseguendo in parallelo, ma in realtà stanno facendo i turni usando lo stesso core della CPU. Tutto questo passaggio GIL aggiunge sovraccarico all'esecuzione. Ciò significa che se si desidera rendere più veloce l'esecuzione del codice, l'utilizzo del pacchetto di threading spesso non è una buona idea.

Ci sono ragioni per usare il pacchetto di threading di Python. Se vuoi eseguire alcune cose contemporaneamente, e l'efficienza non è un problema, allora è totalmente a posto e conveniente. Oppure, se stai eseguendo un codice che deve attendere qualcosa (come alcuni IO), potrebbe avere molto senso. Ma la libreria di threading non ti permetterà di usare core di CPU extra.

Il multi-threading può essere esternalizzato al sistema operativo (eseguendo il multi-processing), alcune applicazioni esterne che chiamano il tuo codice Python (ad esempio Spark o Hadoop) o alcuni codici che il tuo codice Python chiama (ad esempio: potresti avere il tuo Python il codice chiama una funzione C che fa le costose cose multi-thread).


15

Ogni volta che due thread hanno accesso alla stessa variabile hai un problema. In C ++, ad esempio, il modo per evitare il problema è definire un blocco mutex per evitare che due thread, diciamo, entrino nel setter di un oggetto contemporaneamente.

Il multithreading è possibile in Python, ma non è possibile eseguire due thread contemporaneamente con una granularità più fine di un'istruzione Python. Il thread in esecuzione sta ottenendo un blocco globale chiamato GIL.

Questo significa che se inizi a scrivere del codice multithread per sfruttare il tuo processore multicore, le tue prestazioni non miglioreranno. La soluzione alternativa consueta consiste nell'andare multiprocesso.

Si noti che è possibile rilasciare GIL se ci si trova all'interno di un metodo scritto in C per esempio.

L'uso di un GIL non è inerente a Python ma ad alcuni dei suoi interpreti, incluso il CPython più comune. (#edited, vedi commento)

Il problema GIL è ancora valido in Python 3000.


Stackless ha ancora un GIL. Stackless non migliora il threading (come in, il modulo): offre un metodo di programmazione diverso (coroutine) che tenta di aggirare il problema, ma richiede funzioni non bloccanti.
jnoller,

Che dire del nuovo GIL in 3.2?
nuovo123456

Solo per aggiungere che non hai problemi / hai bisogno di mutex / semafori se solo un thread aggiornerà la memoria. @ new123456 riduce la contesa e pianifica meglio i thread senza danneggiare le prestazioni a thread singolo (che è di per sé impressionante) ma è ancora un blocco globale.
Base

14

Documentazione di Python 3.7

Vorrei anche evidenziare la seguente citazione dalla documentazione di Pythonthreading :

Dettagli dell'implementazione di CPython: in CPython, a causa del Global Interpreter Lock, solo un thread può eseguire il codice Python alla volta (anche se alcune librerie orientate alle prestazioni potrebbero superare questa limitazione). Se si desidera che l'applicazione utilizzi meglio le risorse computazionali delle macchine multi-core, si consiglia di utilizzare multiprocessingo concurrent.futures.ProcessPoolExecutor. Tuttavia, il threading è ancora un modello appropriato se si desidera eseguire più attività associate a I / O contemporaneamente.

Questo si collega alla voceglobal interpreter lock del Glossario per la quale spiega che il GIL implica che il parallelismo thread in Python non è adatto per le attività legate alla CPU :

Il meccanismo utilizzato dall'interprete CPython per assicurare che solo un thread esegua il bytecode Python alla volta. Ciò semplifica l'implementazione di CPython rendendo il modello a oggetti (compresi i tipi critici incorporati come dict) implicitamente sicuro contro l'accesso simultaneo. Il blocco dell'intero interprete rende più semplice l'interprete multi-thread, a scapito di gran parte del parallelismo offerto dalle macchine multiprocessore.

Tuttavia, alcuni moduli di estensione, standard o di terze parti, sono progettati in modo da rilasciare GIL quando si eseguono attività ad alta intensità computazionale come la compressione o l'hash. Inoltre, il GIL viene sempre rilasciato quando si esegue l'I / O.

Gli sforzi passati per creare un interprete "a thread libero" (uno che blocca i dati condivisi con una granularità molto più fine) non hanno avuto successo perché le prestazioni hanno sofferto nel caso comune di un singolo processore. Si ritiene che il superamento di questo problema di prestazioni renderebbe l'implementazione molto più complicata e quindi più costosa da mantenere.

Questa citazione implica anche che dicts e quindi l'assegnazione variabile sono anche thread-safe come dettaglio di implementazione di CPython:

Successivamente, i documenti per il multiprocessingpacchetto spiegano come supera il GIL generando il processo mentre espone un'interfaccia simile a quella di threading:

multiprocessing è un pacchetto che supporta i processi di generazione utilizzando un'API simile al modulo threading. Il pacchetto multiprocessore offre sia la concorrenza locale sia quella remota, facendo da supporto laterale al Global Interpreter Lock utilizzando sottoprocessi anziché thread. Per questo motivo, il modulo multiprocessore consente al programmatore di sfruttare appieno più processori su una determinata macchina. Funziona sia su Unix che su Windows.

E i documenti perconcurrent.futures.ProcessPoolExecutor spiegare che utilizza multiprocessingcome backend:

La classe ProcessPoolExecutor è una sottoclasse Executor che utilizza un pool di processi per eseguire le chiamate in modo asincrono. ProcessPoolExecutor utilizza il modulo multiprocessing, che consente di aggirare lateralmente il Global Interpreter Lock ma significa anche che è possibile eseguire e restituire solo oggetti selezionabili.

che dovrebbe essere contrastato con l'altra classe di base ThreadPoolExecutorche utilizza i thread anziché i processi

ThreadPoolExecutor è una sottoclasse Executor che utilizza un pool di thread per eseguire le chiamate in modo asincrono.

da cui concludiamo che ThreadPoolExecutorè adatto solo per attività associate a I / O, mentre ProcessPoolExecutorpuò anche gestire attività associate alla CPU.

La seguente domanda chiede perché il GIL esiste in primo luogo: perché il Global Interpreter Lock?

Esperimenti di processo vs thread

In Multiprocessing vs Threading Python ho fatto un'analisi sperimentale del processo vs thread in Python.

Anteprima rapida dei risultati:

inserisci qui la descrizione dell'immagine


0

Perché Python (CPython e altri) utilizza GIL

Da http://wiki.python.org/moin/GlobalInterpreterLock

In CPython, il blocco dell'interprete globale, o GIL, è un mutex che impedisce a più thread nativi di eseguire bytecode Python contemporaneamente. Questo blocco è necessario principalmente perché la gestione della memoria di CPython non è thread-safe.

Come rimuoverlo da Python?

Come Lua, forse Python potrebbe avviare più VM, ma python non lo fa, immagino che ci dovrebbero essere altri motivi.

In Numpy o in qualche altra libreria estesa di Python, a volte, il rilascio di GIL su altri thread potrebbe aumentare l'efficienza dell'intero programma.


0

Voglio condividere un esempio dal multithreading del libro per Visual Effects. Quindi ecco una classica situazione di dead lock

static void MyCallback(const Context &context){
Auto<Lock> lock(GetMyMutexFromContext(context));
...
EvalMyPythonString(str); //A function that takes the GIL
...    
}

Ora considera gli eventi nella sequenza risultanti in un dead-lock.

╔═══╦════════════════════════════════════════╦══════════════════════════════════════╗
    Main Thread                             Other Thread                         
╠═══╬════════════════════════════════════════╬══════════════════════════════════════╣
 1  Python Command acquires GIL             Work started                         
 2  Computation requested                   MyCallback runs and acquires MyMutex 
 3                                          MyCallback now waits for GIL         
 4  MyCallback runs and waits for MyMutex   waiting for GIL                      
╚═══╩════════════════════════════════════════╩══════════════════════════════════════╝
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.