Questo tutorial sarà incentrato su uno degli argomenti importanti di Python, GIL. Tratteremo anche l'impatto del GIL sulle prestazioni dei programmi Python con l'implementazione del codice. Prima di approfondire questo argomento, diamo un'idea di base del GIL.
GIL o blocco interprete globale
Python Global Interpreter Lock o GIL è una parte importante della programmazione multithreading. È un tipo di blocco del processo utilizzato quando si lavora con più processi. Dà il controllo a un solo thread. Generalmente, Python utilizza un singolo thread per eseguire un singolo processo. Otteniamo lo stesso risultato prestazionale dei processi a thread singolo e multi-thread utilizzando GIL. Limita il raggiungimento del multithreading in Python perché impedisce i thread e funziona come un singolo thread.
Nota: Python non supporta il multithreading perché i pacchetti di threading non potrebbero consentirci di utilizzare più core della CPU.
Perché gli sviluppatori Python utilizzano GIL?
Python fornisce la funzionalità unica del contatore di riferimenti, che viene utilizzata per la gestione della memoria. Il contatore dei riferimenti conta il numero totale di riferimenti effettuati internamente in Python per assegnare un valore a un oggetto dati. Quando i conteggi dei riferimenti raggiungono lo zero, la memoria assegnata all'oggetto viene rilasciata. Vediamo l'esempio qui sotto.
Esempio -
import sys a = [] b = a sys.getrefcount(a)
La preoccupazione principale con la variabile di conteggio dei riferimenti è che può essere influenzata quando due o tre thread tentano di aumentare o diminuire il suo valore contemporaneamente. È noto come condizione di gara. Se si verifica questa condizione, è possibile che si verifichi una perdita di memoria che non viene mai rilasciata. Potrebbe bloccarsi o presentare bug nel programma Python.
GIL ci aiuta a rimuovere una situazione del genere utilizzando i blocchi su tutte le strutture dati condivise tra i thread in modo che non vengano modificate in modo incoerente. Python fornisce un modo semplice per implementare GIL poiché si occupa della gestione della memoria thread-safe. GIL richiede l'offerta di un singolo blocco a un thread per l'elaborazione in Python. Aumenta le prestazioni di un programma a thread singolo poiché è necessario gestire un solo blocco. Aiuta anche a creare qualsiasi programma legato alla CPU e previene la condizione di deadlock.
L'impatto sui programmi Python multi-thread
C'è una differenza tra i limiti della CPU nelle loro prestazioni e i limiti di I/O per un tipico programma Python o qualsiasi programma per computer. I programmi legati alla CPU generalmente spingono la CPU ai suoi limiti. Questi programmi sono generalmente utilizzati per calcoli matematici come moltiplicazioni di matrici, bruciatura, elaborazione di immagini, ecc.
I programmi associati a I/O sono quei programmi che impiegano tempo per ottenere input/output che possono essere generati dall'utente, dal file, dal database, dalla rete, ecc. Tali programmi devono attendere una quantità significativa di tempo finché la sorgente non fornisce l'input. D'altro canto anche la sorgente ha il proprio tempo di elaborazione. Ad esempio, un utente sta pensando a cosa inserire come input.
Comprendiamo il seguente esempio.
Esempio -
coda prioritaria
import time from threading import Thread COUNT = 100000000 def countdown(num): while num>0: num -= 1 start_time = time.time() countdown(COUNT) end_time = time.time() print('Time taken in seconds -', end_time - start_time)
Produzione:
Time taken in seconds - 7.422671556472778
Ora modifichiamo il codice sopra eseguendo i due thread.
Esempio - 2:
import time from threading import Thread COUNT = 100000000 def countdown(num): while num>0: num -= 1 thread1 = Thread(target=countdown, args=(COUNT//2,)) thread2 = Thread(target=countdown, args=(COUNT//2,)) start_time = time.time() thread1.start() thread2.start() thread1.join() thread2.join() end_time = time.time() print('Time taken in seconds -', end_time - start_time)
Produzione:
Time taken in seconds - 6.90830135345459
Come possiamo vedere, entrambi i codici hanno impiegato lo stesso tempo per terminare. GIL ha impedito l'esecuzione in parallelo dei thread legati alla CPU nel secondo codice.
Perché il GIL non è stato ancora rimosso?
Molti programmatori si lamentano di questo, ma Python non può apportare modifiche così significative come la rimozione di GIL. Un altro motivo è che GIL non è migliorato al momento. Se cambia in Python 3, creerà alcuni problemi seri. Invece di rimuovere GIL, il concetto di GIL può essere migliorato. Secondo Guido van Rossom -
'Apprezzerei una serie di patch in Py3k solo se le prestazioni di un programma a thread singolo (e di un programma multi-thread ma con I/O associato) non diminuiscono'.
Sono disponibili anche molti metodi che risolvono lo stesso problema risolto dal GIL, ma sono difficili da implementare.
Come gestire il GIL di Python
L'uso del multiprocessing è il modo più adatto per impedire al programma di GIL. Python offre vari interpreti per l'esecuzione di ciascun processo, quindi in quello scenario viene fornito il thread singolo a ciascun processo in multiprocessing. Comprendiamo il seguente esempio.
Esempio -
from multiprocessing import Pool import time COUNT = 50000000 def countdown(num): while num>0: num -= 1 if __name__ == '__main__': pool = Pool(processes=2) start_time = time.time() r1 = pool.apply_async(countdown, [COUNT//2]) r2 = pool.apply_async(countdown, [COUNT//2]) pool.close() pool.join() end_time = time.time() print('Time taken in seconds -', end_time - start_time)
Produzione:
Time taken in seconds - 3.3707828521728516
Può sembrare che una prestazione decente venga aumentata, ma la gestione dei processi ha i suoi costi generali e più processi sono più pesanti di più thread.
Conclusione
In questo tutorial, abbiamo discusso del GIL e di come possiamo usarlo. Dà il controllo al singolo thread da eseguire alla volta. Questo tutorial spiega anche perché GIL è importante per i programmatori Python.