Multithreading in Python [con esempi di codifica]
Pubblicato: 2020-11-30Migliorare e rendere più veloce il codice è il passo successivo dopo l'acquisizione delle conoscenze di base di Python. Il multithreading è uno di questi modi per ottenere tale ottimizzazione utilizzando "Threads". Quali sono questi thread? E in che modo questi sono diversi dai processi? Scopriamolo.
Alla fine di questo tutorial, avrai la conoscenza di quanto segue:
- Cosa sono i thread e i processi?
- Come si ottiene il multithreading?
- Quali sono i suoi limiti?
- Quando usare il multithreading?
Impara i corsi di scienza dei dati online dalle migliori università del mondo. Guadagna programmi Executive PG, programmi di certificazione avanzati o programmi di master per accelerare la tua carriera.
Sommario
Discussioni in Python
Quando pensiamo al multitasking, pensiamo all'esecuzione parallela. Esecuzione multithreading non strettamente parallela. I thread possono essere considerati come entità separate del flusso di esecuzione di diverse parti del programma in esecuzione in modo indipendente. Quindi, essenzialmente, i thread non vengono eseguiti in parallelo, ma Python passa da un thread all'altro così velocemente che sembra che siano paralleli.
I processi, d'altra parte, sono rigorosamente paralleli e vengono eseguiti su core diversi per ottenere un'esecuzione più rapida. I thread possono anche essere eseguiti su processori diversi, ma tecnicamente non funzioneranno in parallelo.
E stai pensando che se i thread non funzionano in parallelo, come fanno a rendere le cose più veloci? La risposta è che non sempre rendono l'elaborazione più veloce. Il multithreading viene utilizzato specificamente nelle attività in cui i thread renderanno l'elaborazione più veloce.
Tutte le informazioni di un thread sono contenute nel Thread Control Block (TCB) . TCB è costituito dalle seguenti parti principali:
- Un TID univoco: identificatore di thread
- Stack Pointer che punta allo stack del thread nel processo
- Un contatore di programma che memorizza l'indirizzo dell'istruzione attualmente in esecuzione dal thread
- Stato del thread (in esecuzione, pronto, in attesa, avviato o terminato)
Detto questo, i processi possono contenere più thread che condividono il codice, i dati e tutti i file. E tutti i thread hanno il proprio registro e stack separati a cui hanno accesso.
Ora potresti chiederti, se i thread utilizzano i dati e il codice comuni, come possono tutti usarlo senza ostacolare altri thread. Questa è la più grande limitazione del multithreading di cui parleremo più avanti in questo tutorial.
Cambio di contesto
Ora, come descritto sopra, i thread non vengono eseguiti in parallelo, ma di conseguenza. Quindi, quando un thread T1 inizia l'esecuzione, tutti gli altri thread rimangono in modalità di attesa. Solo dopo che T1 ha terminato la sua esecuzione, qualsiasi altro thread in coda può iniziare a essere eseguito. Python passa da un thread all'altro così velocemente che sembra un'esecuzione parallela. Questo passaggio è ciò che chiamiamo 'cambio di contesto'.
Programmazione multithread
Considera di seguito il codice che utilizza i thread per eseguire un'operazione sul cubo e sul quadrato.
importare il threading def cubo (n) : print( “Cubo: {}” .format(n * n * n)) def quadrato (n) : print( “Quadrato: {}” .format(n * n)) if __name__ == “__main__” : # crea il thread t1 = threading.Thread(target=squarer, args=( 5 ,)) t2 = threading.Thread(target=cuber, args=( 5 ,)) # avvia il thread t1 t1.start() # avvia il thread t2 t2.start() # attendere che t1 sia completato t1.join() # attendere che t2 sia completato t2.join() # entrambi i thread completati print( “Fatto!” ) |
#Produzione: Piazza: 25 Cubo: 125 Fatto! |
Ora proviamo a capire il codice.
Innanzitutto, importiamo il modulo Threading che è responsabile di tutte le attività. All'interno del main creiamo 2 thread creando delle sottoclassi della classe Thread. Abbiamo bisogno di passare il target, che è la funzione che deve essere eseguita in quel thread, e gli argomenti che devono essere passati in quelle funzioni.
Ora, una volta dichiarati i thread, dobbiamo avviarli. Ciò viene fatto chiamando il metodo start sui thread. Una volta avviato, il programma principale deve attendere che i thread terminino la loro elaborazione. Usiamo il metodo wait per mettere in pausa il programma principale e attendere che i thread T1 e T2 terminino la loro esecuzione.
Da leggere: Python Challenges per principianti
Sincronizzazione dei thread
Come discusso in precedenza, i thread non vengono eseguiti in parallelo, invece Python passa da uno all'altro. Quindi, c'è un'esigenza molto critica di una corretta sincronizzazione tra i thread per evitare comportamenti strani.
Condizione di gara
I thread che sono sottoposti allo stesso processo utilizzano dati e file comuni che possono portare a una "gara" per i dati tra più thread. Pertanto, se si accede a un dato da più thread, verrà modificato da entrambi i thread e i risultati che otterremo non saranno quelli previsti. Questa è chiamata una condizione di razza.
Quindi, se hai due thread che hanno accesso agli stessi dati, entrambi possono accedervi e modificarli quando quel particolare thread è in esecuzione. Quindi, quando T1 inizia l'esecuzione e modifica alcuni dati, T2 è in modalità di sospensione/attesa. Quindi T1 interrompe l'esecuzione e va in modalità sospensione consegnando il controllo a T2, che ha anche accesso agli stessi dati. Quindi T2 ora modificherà e sovrascriverà gli stessi dati che porteranno a problemi quando T1 ricomincia.

Lo scopo della sincronizzazione dei thread è assicurarsi che questa condizione di competizione non si verifichi mai e che i thread accedano alla sezione critica del codice uno alla volta in modo sincronizzato.
Serrature
Per risolvere e prevenire la Race Condition e le sue conseguenze, il modulo thread offre una classe Lock che usa i semafori per aiutare i thread a sincronizzarsi. I semafori non sono altro che flag binari. Considerali come il segno "Impegnato" sulle cabine telefoniche che hanno il valore di "Impegnato" (equivalente a 1) o "Non impegnato" (equivalente a 0). Quindi ogni volta che un thread incontra un segmento di codice con blocco, deve verificare se il blocco è già in 1 stato. In tal caso, dovrà attendere fino a quando non diventa 0 in modo da poterlo utilizzare.
La classe Lock ha due metodi principali:
- acquisisci([blocco]) : il metodo acquisisce accetta il blocco del parametro come True o False . Se un blocco per un thread T1 è stato avviato con il blocco come True, attenderà o rimarrà bloccato fino a quando la sezione critica del codice non verrà bloccata da un altro thread T2. Una volta che l'altro thread T2 ha rilasciato il lock, il thread T1 acquisisce il lock e restituisce True .
Se invece il blocco per il thread T1 è stato avviato con il blocco dei parametri come False , il thread T1 non attenderà o rimarrà bloccato se la sezione critica è già bloccata dal thread T2. Se lo vede come bloccato, tornerà immediatamente False e uscirà. Tuttavia, se il codice non è stato bloccato da un altro thread, acquisirà il blocco e restituirà True .
release() : Quando il metodo release viene chiamato sul blocco, sbloccherà il blocco e restituirà True. Inoltre, verificherà se alcuni thread sono in attesa di rilascio del blocco. Se ce ne sono, consentirà esattamente a uno di loro di accedere al lucchetto.
Tuttavia, se il blocco è già sbloccato, viene generato un ThreadError.
Deadlock
Un altro problema che sorge quando ci occupiamo di più blocchi è: i deadlock. I deadlock si verificano quando i blocchi non vengono rilasciati dai thread per vari motivi. Consideriamo un semplice esempio in cui facciamo quanto segue:
importare il threading l = threading.Lock() # Prima della 1a acquisizione l.acquisire() # Prima della 2a acquisizione l.acquisire() # Ora acquisito il blocco due volte |
Nel codice precedente, chiamiamo il metodo di acquisizione due volte ma non lo rilasciamo dopo che è stato acquisito per la prima volta. Quindi, quando Python vede la seconda istruzione di acquisizione, entrerà in modalità di attesa indefinitamente poiché non abbiamo mai rilasciato il blocco precedente.
Queste condizioni di deadlock potrebbero insinuarsi nel tuo codice senza che tu te ne accorga. Anche se includi una chiamata di rilascio, il tuo codice potrebbe non riuscire a metà e il rilascio non verrà mai chiamato e il lucchetto rimarrà bloccato. Un modo per superare questo problema è usare l' istruzione with – as , chiamata anche Context Manager. Utilizzando la dichiarazione with – as , il blocco verrà automaticamente rilasciato una volta che l'elaborazione è terminata o non è riuscita per qualsiasi motivo.
Leggi: Idee e argomenti del progetto Python
Prima che tu vada
Come discusso in precedenza, il multithreading non è utile in tutte le applicazioni poiché non fa funzionare le cose in parallelo. Ma l'applicazione principale del multithreading è durante le attività di I/O in cui la CPU rimane inattiva in attesa del caricamento dei dati. Il multithreading gioca un ruolo cruciale in questo caso poiché questo tempo di inattività della CPU viene utilizzato in altre attività, rendendolo quindi ideale per l'ottimizzazione.
Se sei curioso di conoscere la scienza dei dati, dai un'occhiata al programma Executive PG in Data Science di IIIT-B e upGrad, creato per i professionisti che lavorano e offre oltre 10 casi di studio e progetti, workshop pratici pratici, tutoraggio con esperti del settore, 1 -on-1 con mentori del settore, oltre 400 ore di apprendimento e assistenza al lavoro con le migliori aziende.
Che cos'è un thread in Python?
I thread sono entità all'interno di un processo che possono essere pianificate per l'esecuzione in Python. In parole povere, un thread è un processo di calcolo eseguito da un computer. È un insieme di tali istruzioni all'interno di un programma che gli sviluppatori possono eseguire indipendentemente da altri script. I thread consentono di aumentare la velocità dell'applicazione utilizzando il parallelismo. È un processo leggero che consentirà alle attività di funzionare in parallelo. I thread operano in modo indipendente e massimizzano l'utilizzo della CPU, migliorando così le prestazioni della CPU.
A cosa serve il multithread in Python?
Il multithreading è una tecnica di threading nella programmazione Python che consente a molti thread di operare contemporaneamente passando rapidamente da un thread all'altro con l'assistenza di una CPU (chiamato cambio di contesto). Quando possiamo dividere il nostro compito in più sezioni separate, utilizziamo il multithreading. Si supponga, ad esempio, di dover eseguire una query di database complessa per ottenere dati e suddividere tale query in numerose query individuali. In tal caso, sarà preferibile allocare un thread a ciascuna query ed eseguirli tutti in parallelo.
Che cos'è la sincronizzazione dei thread?
La sincronizzazione dei thread è descritta come un metodo che garantisce che due o più processi o thread simultanei non eseguano contemporaneamente una parte cruciale di un programma. I metodi di sincronizzazione vengono utilizzati per controllare l'accesso dei processi a sezioni importanti. Quando si avviano due o più thread all'interno di un programma, esiste la possibilità che più thread tenti di accedere alla stessa risorsa, ottenendo risultati imprevisti a causa di problemi di concorrenza. Ad esempio, se molti thread tentano di scrivere all'interno dello stesso file, i dati potrebbero essere danneggiati perché uno dei thread può sovrascrivere i dati o quando un thread si apre e un altro thread chiude lo stesso file.