Multithreading în Python [cu exemple de codare]

Publicat: 2020-11-30

Îmbunătățirea și accelerarea codului este următorul pas după dobândirea cunoștințelor de bază despre Python. Multithreading este o astfel de modalitate de a realiza acea optimizare folosind „Fire”. Ce sunt firele astea? Și prin ce sunt acestea diferite de procese? Să aflăm.

Până la sfârșitul acestui tutorial, veți avea cunoștințele despre următoarele:

  • Ce sunt firele și procesele?
  • Cum se realizează Multithreading?
  • Care sunt limitările sale?
  • Când să folosiți Multithreading?

Învață cursuri online de știință a datelor de la cele mai bune universități din lume. Câștigă programe Executive PG, programe avansate de certificat sau programe de master pentru a-ți accelera cariera.

Cuprins

Fire în Python

Când ne gândim la multitasking, ne gândim la execuție paralelă. Multithreading nu execuție strict paralelă. Threadurile pot fi considerate ca entități separate ale fluxului de execuție a diferitelor părți ale programului dumneavoastră care rulează independent. Deci, în esență, firele de execuție nu se execută în paralel, dar Python trece de la un fir de execuție la altul atât de repede încât pare că sunt paralele.

Procesele, pe de altă parte, sunt strict paralele și rulează pe nuclee diferite pentru a realiza execuția mai rapidă. Threadurile pot fi, de asemenea, rulate pe procesoare diferite, dar tot nu vor rula în paralel din punct de vedere tehnic.

Și te gândești că dacă firele nu rulează paralel, atunci cum fac lucrurile mai repede? Răspunsul este că nu fac întotdeauna procesarea mai rapidă. Multithreading-ul este utilizat în mod special în sarcinile în care firele vor face procesarea mai rapidă.

Toate informațiile unui thread sunt conținute în Thread Control Block (TCB) . TCB constă din următoarele părți principale:

  1. Un TID unic – Identificator de fir
  2. Stack Pointer care indică stiva firului de execuție în proces
  3. Un contor de program care stochează adresa instrucțiunii care este executată în prezent de firul de execuție
  4. Starea firului de execuție (rulează, gata, așteaptă, începe sau gata)

Acestea fiind spuse, procesele pot conține mai multe fire care partajează codul, datele și toate fișierele. Și toate firele au propriul lor registru și stivă separată la care au acces.

Acum s-ar putea să vă întrebați, dacă firele folosesc datele și codul obișnuit, cum le pot folosi toate fără a împiedica alte fire. Aceasta este cea mai mare limitare a Multithreading-ului despre care vom vorbi mai târziu în acest tutorial.

Schimbarea contextului

Acum, așa cum este descris mai sus, firele nu rulează paralel, ci în consecință. Deci, când un fir T1 începe execuția, toate celelalte fire rămân în modul de așteptare. Numai după ce T1 s-a terminat cu execuția sa poate începe să se execute orice alt fir din coadă. Python trece de la un fir la altul atât de repede încât pare o execuție paralelă. Această comutare este ceea ce numim „Schimbarea contextului”.

Programare cu mai multe fire

Luați în considerare codul de mai jos care utilizează fire pentru a efectua o operație cub și pătrat.

import threading

def cuber (n) :
print( „Cub: {}” .format(n * n * n))

def squarer (n) :
print( „Pătrat: {}” .format(n * n))

if __name__ == „__main__” :
# creați firul
t1 = threading.Thread(țintă=pătrat, args=( 5 ,))
t2 = threading.Thread(target=cuber, args=( 5 ,))

# începe firul t1
t1.start()
# începe firul t2
t2.start()

# așteptați până la finalizarea t1
t1.join()
# așteptați până la finalizarea t2
t2.join()

# ambele fire au fost finalizate
print( „Gata!” )

#Ieșire:
Patrat: 25
Cub: 125
Terminat!

Acum să încercăm să înțelegem codul.

Mai întâi, importăm modulul Threading, care este responsabil pentru toate sarcinile. În interiorul principal, creăm 2 fire prin crearea de subclase ale clasei Thread. Trebuie să trecem ținta, care este funcția care trebuie executată în acel fir de execuție, și argumentele care trebuie să fie transmise în acele funcții.

Acum, odată ce firele sunt declarate, trebuie să le pornim. Acest lucru se face apelând metoda start pe fire. Odată pornit, programul principal trebuie să aștepte ca firele să își termine procesarea. Folosim metoda wait pentru a lăsa programul principal să se oprească și să așteptăm ca firele T1 și T2 să își termine execuția.

Trebuie citit: Provocări Python pentru începători

Sincronizarea firelor

După cum am discutat mai sus, firele de execuție nu se execută în paralel, ci Python comută de la unul la altul. Deci, există o nevoie foarte critică de sincronizare corectă între fire pentru a evita orice comportament ciudat.

Condiție de cursă

Firele care se află sub același proces folosesc date și fișiere comune care pot duce la o „cursă” pentru datele dintre mai multe fire. Prin urmare, dacă o bucată de date este accesată de mai multe fire de execuție, aceasta va fi modificată de ambele fire de execuție, iar rezultatele pe care le vom obține nu vor fi cele așteptate. Aceasta se numește o condiție de cursă.

Deci, dacă aveți două fire de execuție care au acces la aceleași date, atunci ambele le pot accesa și modifica atunci când acel fir de execuție se execută. Deci, când T1 începe să execute și modifică unele date, T2 este în modul de repaus/așteptare. Apoi T1 oprește execuția și intră în modul de repaus predând controlul lui T2, care are și acces la aceleași date. Deci T2 va modifica acum și va suprascrie aceleași date, ceea ce va duce la probleme când T1 va începe din nou.

Scopul Sincronizării firelor este de a se asigura că această condiție de cursă nu vine niciodată și că secțiunea critică a codului este accesată de fire pe rând într-un mod sincronizat.

Încuietori

Pentru a rezolva și a preveni condiția de cursă și consecințele acesteia, modulul de fire oferă o clasă de blocare care utilizează semafore pentru a ajuta firele de execuție să se sincronizeze. Semaforele nu sunt altceva decât steaguri binare. Considerați-le drept semnul „Angajat” de pe cabinele telefonice, care au valoarea „Angajat” (echivalent cu 1) sau „Nefiind angajat” (echivalent cu 0). Deci, de fiecare dată când un fir de execuție întâlnește un segment de cod cu blocare, trebuie să verifice dacă blocarea este deja într-o stare. Dacă este, atunci va trebui să aștepte până devine 0 pentru a-l putea folosi.

Clasa Lock are două metode principale:

  1. achiziționare([blocare]) : metoda de achiziție ia în considerare blocarea parametrilor ca fiind Adevărat sau Fals . Dacă o blocare pentru un thread T1 a fost inițiată cu blocarea ca True, va aștepta sau rămâne blocată până când secțiunea critică a codului este blocată de un alt thread T2. Odată ce celălalt fir T2 eliberează blocarea, firul T1 dobândește blocarea și returnează True .

Pe de altă parte, dacă blocarea pentru threadul T1 a fost inițiată cu blocarea parametrilor ca False , threadul T1 nu va aștepta sau rămâne blocat dacă secțiunea critică este deja blocată de threadul T2. Dacă îl vede blocat, va reveni imediat la False și va ieși. Cu toate acestea, dacă codul nu a fost blocat de un alt fir, acesta va obține blocarea și va returna True .

release() : Când metoda de eliberare este apelată pe blocare, aceasta va debloca blocarea și va returna True. De asemenea, va verifica dacă există fire care așteaptă ca blocarea să fie eliberată. Dacă există, atunci va permite exact unuia dintre ei să acceseze încuietoarea.

Cu toate acestea, dacă blocarea este deja deblocată, apare o ThreadError.

Blocaje

O altă problemă care apare atunci când avem de-a face cu mai multe încuietori este – Deadlocks. Blocajele apar atunci când blocările nu sunt eliberate de fire din diverse motive. Să luăm în considerare un exemplu simplu în care facem următoarele:

import threading

l = threading.Lock()
# Înainte de prima achiziție
l.achizitioneaza()
# Înainte de a 2-a achiziție
l.achizitioneaza()
# Acum a obținut blocarea de două ori

În codul de mai sus, apelăm metoda de achiziție de două ori, dar nu o eliberăm după ce este achiziționată pentru prima dată. Prin urmare, atunci când Python vede a doua instrucțiune de achiziție, va intra în modul așteptare pe termen nelimitat, deoarece nu am eliberat niciodată blocarea anterioară.

Aceste condiții de blocaj s-ar putea strecura în codul tău fără să-ți dai seama. Chiar dacă includeți un apel de eliberare, codul dvs. poate eșua la jumătatea drumului și eliberarea nu va fi apelată niciodată, iar blocarea va rămâne blocată. O modalitate de a depăși acest lucru este utilizarea declarației with as , numită și Manageri de context. Folosind declarația cu ca , blocarea va fi eliberată automat odată ce procesarea se încheie sau eșuează din orice motiv.

Citiți: Idei și subiecte pentru proiecte Python

Inainte sa pleci

După cum am discutat mai devreme, Multithreading nu este util în toate aplicațiile, deoarece nu face ca lucrurile să funcționeze în paralel. Dar principala aplicație a Multithreading-ului este în timpul sarcinilor I/O, în care procesorul stă inactiv în timp ce așteaptă încărcarea datelor. Multithreading-ul joacă un rol crucial aici, deoarece acest timp inactiv al procesorului este utilizat în alte sarcini, făcându-l astfel ideal pentru optimizare.

Dacă sunteți curios să aflați despre știința datelor, consultați programul Executive PG în știința datelor de la IIIT-B și upGrad, care este creat pentru profesioniști care lucrează și oferă peste 10 studii de caz și proiecte, ateliere practice practice, mentorat cu experți din industrie, 1 -on-1 cu mentori din industrie, peste 400 de ore de învățare și asistență profesională cu firme de top.

Ce este un thread în Python?

Threadurile sunt entități dintr-un proces care pot fi programate pentru execuție în Python. În termeni profani, un fir este un proces de calcul efectuat de un computer. Este un set de astfel de instrucțiuni în cadrul unui program pe care dezvoltatorii le pot rula independent de alte scripturi. Threadurile vă permit să creșteți viteza aplicației folosind paralelismul. Este un proces ușor, care va permite sarcinilor să funcționeze în paralel. Firele funcționează independent și maximizează utilizarea procesorului, îmbunătățind astfel performanța procesorului.

La ce folosește multi-thread-ul în Python?

Multithreadingul este o tehnică de threading în programarea Python care permite multor fire de execuție să funcționeze concomitent prin comutarea rapidă între fire de execuție cu ajutorul unui CPU (numită comutare de context). Când ne putem împărți sarcina în mai multe secțiuni separate, folosim multithreading. De exemplu, să presupunem că trebuie să efectuați o interogare complexă a bazei de date pentru a obține date și a împărți acea interogare în numeroase interogări individuale. În acest caz, va fi de preferat să aloci câte un fir pentru fiecare interogare și să le rulezi pe toate în paralel.

Ce este sincronizarea firelor?

Sincronizarea firelor de execuție este descrisă ca o metodă care garantează că două sau mai multe procese sau fire de execuție concurente nu execută o parte crucială a unui program simultan. Metodele de sincronizare sunt folosite pentru a controla accesul proceselor la secțiuni importante. Când începem două sau mai multe fire în interiorul unui program, există șansa ca mai multe fire să încerce să acceseze aceeași resursă, rezultând rezultate neașteptate din cauza provocărilor concurenței. De exemplu, dacă multe fire încearcă să scrie în același fișier, datele pot fi corupte deoarece unul dintre fire poate suprascrie datele sau când un fir se deschide și un alt fir închide același fișier.