Una volta superata la prima fase di apprendimento di un linguaggio di programmazione, come Python, si sono acquisite tutte le basi necessarie per lo sviluppo di un qualsiasi programma. Ma passando allo sviluppo di programmi più complessi e contribuendo a diversi progetti, spesso insieme ad altri sviluppatori, si dovranno imparare moltissime altre nozioni, come per esempio i Thread ed il concetto di Threading.
![Thread in Python - Threading (part 1)](https://www.meccanismocomplesso.org/wp-content/uploads/2019/11/Thread-in-Python-Threading-part-1.jpg)
Threading
Il Threading è una tecnica che ci permette di suddividere in diverse parti il nostro programma, per poi eseguirle in maniera indipendente, se non spesso addirittura, concorrenziale. Questa tecnica si basa, appunto, sui thread.
Il Threading è una tecnica efficiente, o meno, a seconda della tecnologia su cui viene utilizzata, e dalle buone regole di implementazione che tale tecnica richiede. Spesso, se questi due fattori non vengono tenuti bene in considerazione, il threading può portare ad una minore efficienza rispetto ad un programma che non ne fa uso, e a volte anche a risultati disastrosi con perdita di dati e comportamenti anomali ed indesiderati.
E’ quindi importante comprendere bene sia i concetti fondamentali di questa tecnica, sia come viene implementata nei vari linguaggi di programmazione (come Python), sia sulle potenzialità e possibilità del computer su cui lo stiamo eseguendo.
Che cosa sono i Thread
In parole semplici, un Thread è un flusso separato di esecuzione. Nei nostri programmi che faranno uso del threading, i thread racchiuderanno una parte del nostro codice che vogliamo eseguire in modo indipendente dal resto del programma. Mentre nella computer science, il termine thread (thread of execution) sta ad indicare la più piccola unità di eseguibile che può essere schedulata in un sistema operativo. Quindi si usa lo stesso termine, ma con significati leggermente diversi.
Quindi i thread sono legati sia alla struttura del codice (parti di codice) che al sistema operativo e numero di processori presenti nel sistema. Queste due cose spesso non combaciano, ma anzi lavorano su livelli diversi. I thread dei linguaggi di programmazione sono ad alto livello, mentre i thread inviati a processori lavorano a basso livello. Come poi i thread ad alto livello vengano implementati a basso livello da interpreti e compilatori sono tutta un’altra storia.
Un altro aspetto che spesso può portare confusione con i thread, è il concetto di concorrenza. Spesso quando parliamo di thread, pensiamo immediatamente alla possibilità di avere un programma che esegue contemporaneamente due diverse parti di codice. Ma in realtà i vari thread presenti in un programma, e stessa cosa, a livello più basso, i thread di un sistema operativo, non vengono eseguiti contemporaneamente, anche se sembra che lo facciano.
Questo vale anche se si lavora su sistemi con più processori. Infatti, con Python, i diversi thread potranno essere eseguiti su processori differenti, ma verranno eseguiti solo uno alla volta. Per poter ottenere invece che diverse operazioni vengano eseguite tutte simultaneamente, è necessario utilizzare un’implementazione non standard di Python, oppure fare uso del modulo multiprocessing. Questo concetto lo vedremo in dettaglio in un altro articolo.
Approfondimenti sui Thread
Una volta chiariti i concetti che potrebbero portare a confusione, e avuta una prima occhiata sull’argomento dei thread. Scendiamo un po’ più in dettaglio aggiungendo altri concetti.
Partiamo da un programma in generale che comincia la sua esecuzione divenendo un processo. Ogni processo si può considerare esso stesso un thread (main thread)
Se nel codice si fa uso della tecnica di threading, si formeranno uno o più thread in un punto particolare dell’esecuzione, chiamto fork. I thread creati condividono tra di loro sia la memoria che lo stato del processo. In altre parole condividono sia il codice, le istruzioni che i valori delle variabili. Quindi se un thread cambierà una variabile globale, questa cambierà per tutti gli altri thread. Comunque un thread ha anche a disposizione variabili locali che saranno accessibili esclusivamente al singolo thread e scompariranno al termine della sua esecuzione.
A questo punto i diversi thread prendono via a vie autonome di esecuzione (sia che vengano poi eseguiti realmente in parallelo o meno). Anche in un sistema a singolo processore si può avere il threading. In questi sistemi si adotta la tecnica del multitasking, che simula l’esecuzione indipendente e parallela dei processi grazie a tecniche di scheduling e di timeslicing.
I Thread in Python
Python, come moltissimi altri linguaggi di programmazione, implementa i thread
Ci sono due moduli che supportano l’uso dei thread in Python:
- Thread (deprecated)
- Threading
Il modulo Thread è stato considerato “deprecated” e quindi non è più da utilizzare quando si vuole sviluppare del nuovo codice in Python, mentre lo possiamo trovare nei vecchi codici. L’interprete lo accetta ancora per mantenere la compatibilità con le versioni più vecchie.
Il Modulo Threading
Il modo migliore per imparare il funzionamento e l’uso di un modulo in Python è quello di cominciare a lavorare con degli esempi.
Nel primo esempio, implementeremo un singolo thread che attende un determinato periodo di tempo (per.es. 10 secondi) prima di visualizzare un messaggio sulla console. Nel corso dell’esecuzione faremo partire 5 thread contemporaneamente.
import time from threading import Thread def test(i): print("thread ", i, " started") time.sleep(10) print("thread ", i, " ended") for i in range(5): t = Thread(target=test, args=(i,)) t.start()
Dal modulo threading si importa Thread, una interfaccia in cui incapsulare una funzione e che corrisponde ad un thread. Il codice di implementazione del thread lo definiamo all’interno di una funzione che chiameremo test(), e che assegneremo all’interfaccia tramite il parametro target. Mentre per passare dei parametri all’interno del thread si usa args.
Quindi nell’esecuzione del programma faremo partire 5 thread in successione tramite un ciclo for. Dopo aver assegnato un thread alla variabile t, il metodo start() farà partire la sua esecuzione.
Eseguiamo:
![Thread in Python - codice 01](https://www.meccanismocomplesso.org/wp-content/uploads/2019/11/Thread-in-Python-codice-01.jpg)
Come si può vedere dai risultati, i 5 thread partono in sequenza. Infatti il ciclo for va avanti, aprendo via via tutti i thread, senza attendere il termine della loro esecuzione (altrimenti non avrebbero senso i thread). Poi da come si può vedere dal risultato, l’ordine con cui questi terminano è però random. E varia da esecuzione ad esecuzione.
Questo semplicissimo esempio oltre che ad introdurre l’uso dei thread, evidenzia uno degli aspetti più importanti: i risultati possono variare da esecuzione ad esecuzione; o meglio: l’ordine di esecuzione varia ogni volta che lanciamo il programma. Poi se i risultati del programma dipendono da quest’ordine anche questi varieranno. E’ quindi importante per chi sviluppa il programma tenerne conto ed evitare comportamenti non voluti.
Conclusioni
In questa prima parte abbiamo introdotto il concetto dei Thread e abbiamo visto un esempio di base in Python. Nella parte seguente vedremo come utilizzare i Join per avere un maggior controllo sui thread.