Threading Python: un'introduzione
Pubblicato: 2022-10-25In questo tutorial imparerai come utilizzare il modulo di threading integrato di Python per esplorare le capacità di multithreading in Python.
A partire dalle basi di processi e thread, imparerai come funziona il multithreading in Python, mentre comprendi i concetti di concorrenza e parallelismo. Imparerai quindi come avviare ed eseguire uno o più thread in Python utilizzando il modulo di threading
integrato.
Iniziamo.
Processi e thread: quali sono le differenze?
Che cos'è un processo?
Un processo è qualsiasi istanza di un programma che deve essere eseguito.
Può essere qualsiasi cosa: uno script Python o un browser Web come Chrome a un'applicazione di videoconferenza. Se avvii Task Manager sul tuo computer e vai su Performance -> CPU , sarai in grado di vedere i processi e i thread attualmente in esecuzione sui core della tua CPU.

Comprensione di processi e thread
Internamente, un processo ha una memoria dedicata che memorizza il codice e i dati corrispondenti al processo.
Un processo è costituito da uno o più thread . Un thread è la più piccola sequenza di istruzioni che il sistema operativo può eseguire e rappresenta il flusso di esecuzione.
Ogni thread ha il proprio stack e registri ma non una memoria dedicata. Tutti i thread associati a un processo possono accedere ai dati. Pertanto, i dati e la memoria sono condivisi da tutti i thread di un processo.

In una CPU con N core, N processi possono essere eseguiti in parallelo alla stessa istanza di tempo. Tuttavia, due thread dello stesso processo non possono mai essere eseguiti in parallelo, ma possono essere eseguiti contemporaneamente. Affronteremo il concetto di concorrenza e parallelismo nella prossima sezione.
Sulla base di ciò che abbiamo imparato finora, riassumiamo le differenze tra un processo e un thread.
Caratteristica | Processi | Filo |
Memoria | Memoria dedicata | Memoria condivisa |
Modalità di esecuzione | Parallelo, simultaneo | simultanea; ma non parallela |
Esecuzione gestita da | Sistema operativo | Interprete di Python |
Multithreading in Python
In Python, il Global Interpreter Lock (GIL) garantisce che solo un thread possa acquisire il blocco ed essere eseguito in qualsiasi momento. Tutti i thread dovrebbero acquisire questo blocco per essere eseguiti. Ciò garantisce che un solo thread possa essere in esecuzione, in un dato momento, ed evita il multithreading simultaneo .
Ad esempio, si considerino due thread, t1
e t2
, dello stesso processo. Poiché i thread condividono gli stessi dati quando t1
legge un valore k
particolare, t2
può modificare lo stesso valore k
. Questo può portare a deadlock e risultati indesiderati. Ma solo uno dei thread può acquisire il blocco ed essere eseguito in qualsiasi istanza. Pertanto, GIL garantisce anche la sicurezza del thread .
Quindi, come possiamo ottenere capacità di multithreading in Python? Per capirlo, discutiamo i concetti di concorrenza e parallelismo.
Concorrenza vs. Parallelismo: una panoramica
Considera una CPU con più di un core. Nell'illustrazione seguente, la CPU ha quattro core. Ciò significa che possiamo avere quattro diverse operazioni in esecuzione in parallelo in un dato istante.
Se sono presenti quattro processi, ciascuno dei processi può essere eseguito in modo indipendente e simultaneo su ciascuno dei quattro core. Supponiamo che ogni processo abbia due thread.

Per capire come funziona il threading, passiamo dall'architettura del processore multicore a quella single-core. Come accennato, solo un singolo thread può essere attivo in una particolare istanza di esecuzione; ma il core del processore può passare da un thread all'altro.

Ad esempio, i thread legati all'I/O spesso attendono operazioni di I/O: lettura dell'input dell'utente, letture del database e operazioni sui file. Durante questo tempo di attesa, può rilasciare il blocco in modo che l'altro thread possa essere eseguito. Il tempo di attesa può anche essere una semplice operazione come dormire per n
secondi.
In sintesi: durante le operazioni di attesa, il thread rilascia il blocco, consentendo al core del processore di passare a un altro thread. Il thread precedente riprende l'esecuzione al termine del periodo di attesa. Questo processo, in cui il core del processore passa da un thread all'altro contemporaneamente, facilita il multithreading.
Se desideri implementare il parallelismo a livello di processo nella tua applicazione, considera invece l'utilizzo del multiprocessing.
Modulo Python Threading: Primi Passi
Python viene fornito con un modulo di threading
che puoi importare nello script Python.
import threading
Per creare un oggetto thread in Python, puoi usare il costruttore Thread
: threading.Thread(...)
. Questa è la sintassi generica che è sufficiente per la maggior parte delle implementazioni di threading:
threading.Thread(target=...,args=...)
Qui,
-
target
è l'argomento della parola chiave che denota una richiamabile Python -
args
è la tupla di argomenti che il target accetta.
Avrai bisogno di Python 3.x per eseguire gli esempi di codice in questo tutorial. Scarica il codice e segui.
Come definire ed eseguire thread in Python
Definiamo un thread che esegue una funzione di destinazione.
La funzione di destinazione è some_func
.
import threading import time def some_func(): print("Running some_func...") time.sleep(2) print("Finished running some_func.") thread1 = threading.Thread(target=some_func) thread1.start() print(threading.active_count())
Analizziamo cosa fa lo snippet di codice sopra:
- Importa il
threading
e i modulitime
. - La funzione
some_func
ha istruzioniprint()
descrittive e include un'operazione di sospensione per due secondi:time.sleep(n)
fa sì che la funzione vada in pausa pern
secondi. - Successivamente, definiamo un thread
thread_1
con l'obiettivo comesome_func
.threading.Thread(target=...)
crea un oggetto thread. - Nota : specificare il nome della funzione e non una chiamata di funzione; usa
some_func
e nonsome_func()
. - La creazione di un oggetto thread non avvia un thread; chiamando il metodo
start()
sull'oggetto thread lo fa. - Per ottenere il numero di thread attivi, utilizziamo la funzione
active_count()
.
Lo script Python è in esecuzione sul thread principale e stiamo creando un altro thread ( thread1
) per eseguire la funzione some_func
in modo che il conteggio dei thread attivi sia due, come si vede nell'output:

# Output Running some_func... 2 Finished running some_func.
Se osserviamo più da vicino l'output, vediamo che all'avvio di thread1
, viene eseguita la prima istruzione print. Ma durante l'operazione di sospensione, il processore passa al thread principale e stampa il numero di thread attivi, senza attendere il completamento dell'esecuzione del thread1
.

In attesa che i thread finiscano l'esecuzione
Se vuoi che thread1
l'esecuzione, puoi chiamare il metodo join()
su di esso dopo aver avviato il thread. In questo modo si attenderà che thread1
l'esecuzione senza passare al thread principale.
import threading import time def some_func(): print("Running some_func...") time.sleep(2) print("Finished running some_func.") thread1 = threading.Thread(target=some_func) thread1.start() thread1.join() print(threading.active_count())
Ora, thread1
ha terminato l'esecuzione prima di stampare il conteggio dei thread attivi. Quindi solo il thread principale è in esecuzione, il che significa che il conteggio dei thread attivi è uno.
# Output Running some_func... Finished running some_func. 1
Come eseguire più thread in Python
Quindi, creiamo due thread per eseguire due diverse funzioni.
Qui, count_down
è una funzione che accetta un numero come argomento e conta alla rovescia da quel numero fino a zero.
def count_down(n): for i in range(n,-1,-1): print(i)
Definiamo count_up
, un'altra funzione Python che conta da zero fino a un determinato numero.
def count_up(n): for i in range(n+1): print(i)
Quando si utilizza la funzione
range()
con la sintassirange(start, stop, step)
, l'stop
del punto finale è escluso per impostazione predefinita.– Per eseguire il conto alla rovescia da un numero specifico a zero, è possibile utilizzare un valore di
step
negativo pari a -1 e impostare il valore distop
su -1 in modo da includere zero.– Allo stesso modo, per contare fino a
n
, devi impostare il valore distop
sun + 1
. Poiché i valori predefiniti distart
estep
sono rispettivamente 0 e 1, è possibile utilizzarerange(n + 1)
per ottenere la sequenza da 0 a n.
Successivamente, definiamo due thread, thread1
e thread2
per eseguire rispettivamente le funzioni count_down
e count_up
. Aggiungiamo istruzioni print
e sleep
di sospensione per entrambe le funzioni.
Quando si creano gli oggetti thread, si noti che gli argomenti della funzione target devono essere specificati come una tupla, nel parametro args
. Poiché entrambe le funzioni ( count_down
e count_up
) accettano un argomento. Dovrai inserire una virgola in modo esplicito dopo il valore. Ciò garantisce che l'argomento venga ancora passato come una tupla, poiché gli elementi successivi vengono dedotti come None
.
import threading import time def count_down(n): for i in range(n,-1,-1): print("Running thread1....") print(i) time.sleep(1) def count_up(n): for i in range(n+1): print("Running thread2...") print(i) time.sleep(1) thread1 = threading.Thread(target=count_down,args=(10,)) thread2 = threading.Thread(target=count_up,args=(5,)) thread1.start() thread2.start()
In uscita:
- La funzione
count_up
viene eseguita suthread2
e conta fino a 5 a partire da 0. - La funzione
count_down
viene eseguita suthread1
con un conto alla rovescia da 10 a 0.
# Output Running thread1.... 10 Running thread2... 0 Running thread1.... 9 Running thread2... 1 Running thread1.... 8 Running thread2... 2 Running thread1.... 7 Running thread2... 3 Running thread1.... 6 Running thread2... 4 Running thread1.... 5 Running thread2... 5 Running thread1.... 4 Running thread1.... 3 Running thread1.... 2 Running thread1.... 1 Running thread1.... 0
Puoi vedere che thread1
e thread2
eseguiti alternativamente, poiché entrambi implicano un'operazione di attesa (sleep). Una volta che la funzione count_up
ha finito di contare fino a 5, thread2
non è più attivo. Quindi otteniamo l'output corrispondente solo a thread1
.
Riassumendo
In questo tutorial, hai imparato come utilizzare il modulo di threading integrato di Python per implementare il multithreading. Ecco un riassunto dei punti chiave:
- Il costruttore Thread può essere utilizzato per creare un oggetto thread. L'utilizzo di threading.Thread(target=<callable>,args=(<tuple of args>)) crea un thread che esegue il callable di destinazione con gli argomenti specificati in args .
- Il programma Python viene eseguito su un thread principale, quindi gli oggetti thread che crei sono thread aggiuntivi. Puoi chiamare la funzione active_count() restituisce il numero di thread attivi in qualsiasi istanza.
- È possibile avviare un thread utilizzando il metodo start() sull'oggetto thread e attendere che termini l'esecuzione utilizzando il metodo join() .
Puoi codificare esempi aggiuntivi modificando i tempi di attesa, provando un'operazione di I/O diversa e altro ancora. Assicurati di implementare il multithreading nei tuoi prossimi progetti Python. Buona codifica!