Python Threading: o introducere – tipstrick.ro

În acest tutorial, veți învăța cum să utilizați modulul de threading încorporat al lui Python pentru a explora capabilitățile de multithreading în Python.

Începând cu elementele de bază ale proceselor și firelor de execuție, veți învăța cum funcționează multithreadingul în Python, în timp ce înțelegeți conceptele de concurență și paralelism. Veți învăța apoi cum să porniți și să rulați unul sau mai multe fire de execuție în Python folosind modulul de threading încorporat.

Să începem.

Procese vs. fire: care sunt diferențele?

Ce este un proces?

Un proces este orice instanță a unui program care trebuie să ruleze.

Poate fi orice – un script Python sau un browser web, cum ar fi Chrome, la o aplicație de videoconferință. Dacă lansați Managerul de activități pe computer și navigați la Performanță –> CPU, veți putea vedea procesele și firele de execuție care rulează în prezent pe nucleele CPU.

Înțelegerea proceselor și firelor

Intern, un proces are o memorie dedicată care stochează codul și datele corespunzătoare procesului.

Un proces este format din unul sau mai multe fire. Un fir este cea mai mică secvență de instrucțiuni pe care sistemul de operare o poate executa și reprezintă fluxul de execuție.

Fiecare thread are propriul stivă și registre, dar nu o memorie dedicată. Toate firele asociate unui proces pot accesa datele. Prin urmare, datele și memoria sunt partajate de toate firele unui proces.

Într-un CPU cu N nuclee, N procese se pot executa în paralel în același timp. Cu toate acestea, două fire ale aceluiași proces nu se pot executa niciodată în paralel, dar se pot executa simultan. Vom aborda conceptul de concurență vs. paralelism în secțiunea următoare.

Pe baza a ceea ce am învățat până acum, să rezumăm diferențele dintre un proces și un fir.

FeatureProcessThreadMemoryMemorie dedicatăMemorie partajatăMod de execuțieParalel, concurentConcurent; dar nu parallelExecution gestionat de Operating SystemCPython Interpreter

Multithreading în Python

În Python, Global Interpreter Lock (GIL) asigură că un singur fir poate obține blocarea și rula în orice moment. Toate firele ar trebui să obțină această blocare pentru a rula. Acest lucru asigură că numai un singur thread poate fi în execuție — la un moment dat în timp — și evită multithreadingul simultan.

  Ce este HDMI VRR pe PlayStation 5 și Xbox Series X?

De exemplu, luați în considerare două fire, t1 și t2, ale aceluiași proces. Deoarece firele partajează aceleași date atunci când t1 citește o anumită valoare k, t2 poate modifica aceeași valoare k. Acest lucru poate duce la blocaje și rezultate nedorite. Dar numai unul dintre fire poate obține blocarea și rula la orice instanță. Prin urmare, GIL asigură și siguranța firelor.

Deci, cum obținem capabilități de multithreading în Python? Pentru a înțelege acest lucru, să discutăm despre conceptele de concurență și paralelism.

Concurență vs. paralelism: o privire de ansamblu

Luați în considerare un procesor cu mai mult de un nucleu. În ilustrația de mai jos, procesorul are patru nuclee. Aceasta înseamnă că putem avea patru operații diferite care rulează în paralel în orice moment dat.

Dacă există patru procese, atunci fiecare dintre procese poate rula independent și simultan pe fiecare dintre cele patru nuclee. Să presupunem că fiecare proces are două fire.

Pentru a înțelege cum funcționează threading-ul, să trecem de la arhitectura procesorului multicore la arhitectura cu un singur nucleu. După cum sa menționat, doar un singur fir poate fi activ la o anumită instanță de execuție; dar nucleul procesorului poate comuta între fire.

De exemplu, firele de execuție legate de I/O așteaptă adesea operațiuni de I/O: citirea în intrarea utilizatorului, citirile bazei de date și operațiunile cu fișiere. În acest timp de așteptare, poate elibera blocarea, astfel încât celălalt fir să poată rula. Timpul de așteptare poate fi, de asemenea, o operațiune simplă, cum ar fi somnul timp de n secunde.

În rezumat: în timpul operațiunilor de așteptare, firul de execuție eliberează blocarea, permițând nucleului procesorului să comute la un alt thread. Firul anterior reia execuția după încheierea perioadei de așteptare. Acest proces, în care miezul procesorului comută simultan între fire, facilitează multithreading. ✅

Dacă doriți să implementați paralelismul la nivel de proces în aplicația dvs., luați în considerare utilizarea multiprocesării.

Modulul Python Threading: primii pași

Python este livrat cu un modul de threading pe care îl puteți importa în scriptul Python.

import threading

Pentru a crea un obiect thread în Python, puteți folosi constructorul Thread: threading.Thread(…). Aceasta este sintaxa generică care este suficientă pentru majoritatea implementărilor de threading:

threading.Thread(target=...,args=...)

Aici,

  • target este argumentul cuvântului cheie care denotă un apelabil Python
  • args este tuplul de argumente pe care ținta le primește.
  Ce este Forefront AI și este mai bine decât ChatGPT?

Veți avea nevoie de Python 3.x pentru a rula exemplele de cod din acest tutorial. Descărcați codul și urmăriți.

Cum să definiți și să rulați fire în Python

Să definim un fir care rulează o funcție țintă.

Funcția țintă este 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())

Să analizăm ce face fragmentul de cod de mai sus:

  • Importă modulele de threading și de timp.
  • Funcția some_func are instrucțiuni descriptive print() și include o operațiune de somn timp de două secunde: time.sleep(n) determină funcția să intre în somn timp de n secunde.
  • În continuare, definim un thread_1 cu ținta ca some_func. threading.Thread(target=…) creează un obiect thread.
  • Notă: Specificați numele funcției și nu un apel de funcție; folosește some_func și nu some_func().
  • Crearea unui obiect thread nu începe un thread; apelarea metodei start() pe obiectul thread nu.
  • Pentru a obține numărul de fire active, folosim funcția active_count().

Scriptul Python rulează pe firul principal și creăm un alt fir (thread1) pentru a rula funcția some_func, astfel încât numărul de fire active este doi, așa cum se vede în rezultat:

# Output
Running some_func...
2
Finished running some_func.

Dacă aruncăm o privire mai atentă la ieșire, vedem că la pornirea thread1, prima instrucțiune print rulează. Dar în timpul operațiunii de repaus, procesorul trece la firul principal și imprimă numărul de fire active – fără a aștepta ca thread1 să se termine de execuție.

Se așteaptă ca firele să termine execuția

Dacă doriți ca thread1 să termine execuția, puteți apela metoda join() după pornirea firului. Procedând astfel, va aștepta ca thread1 să termine execuția fără a trece la firul principal.

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())

Acum, thread1 s-a terminat de executat înainte de a tipări numărul de fire active. Deci numai firul principal rulează, ceea ce înseamnă că numărul de fire active este unul. ✅

# Output
Running some_func...
Finished running some_func.
1

Cum să rulați mai multe fire în Python

Apoi, să creăm două fire pentru a rula două funcții diferite.

Aici, count_down este o funcție care ia un număr ca argument și numără invers de la acel număr până la zero.

def count_down(n):
    for i in range(n,-1,-1):
        print(i)

Definim count_up, o altă funcție Python care numără de la zero până la un anumit număr.

def count_up(n):
    for i in range(n+1):
        print(i)

📑 Când utilizați funcția range() cu intervalul de sintaxă (start, stop, step), oprirea punctului final este exclusă implicit.

  7 cursuri gratuite Coursera pentru designeri grafici pentru a dezvolta, îmbunătăți sau adapta abilități de design

– Pentru a număra înapoi de la un anumit număr la zero, puteți utiliza o valoare pas negativă de -1 și puteți seta valoarea de oprire la -1, astfel încât zero să fie inclus.

– În mod similar, pentru a număra până la n, trebuie să setați valoarea de oprire la n + 1. Deoarece valorile implicite ale startului și ale pasului sunt 0 și respectiv 1, puteți utiliza intervalul (n + 1) pentru a obține secvența 0 prin n.

În continuare, definim două fire, thread1 și thread2 pentru a rula funcțiile count_down și, respectiv, count_up. Adăugăm instrucțiuni de tipărire și operații de repaus pentru ambele funcții.

Când creați obiectele firului de execuție, observați că argumentele funcției țintă ar trebui specificate ca un tuplu – la parametrul args. Deoarece ambele funcții (count_down și count_up) preiau un singur argument. Va trebui să inserați o virgulă în mod explicit după valoare. Acest lucru asigură că argumentul este încă transmis ca un tuplu, deoarece elementele ulterioare sunt deduse ca Nimic.

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()

În ieșire:

  • Funcția count_up rulează pe thread2 și numără până la 5 începând de la 0.
  • Funcția count_down rulează pe thread1 numărătoare inversă de la 10 la 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

Puteți vedea că thread1 și thread2 se execută alternativ, deoarece ambele implică o operație de așteptare (sleep). Odată ce funcția count_up a terminat de numărat până la 5, thread2 nu mai este activ. Deci obținem rezultatul corespunzătoare numai thread1.

Rezumând

În acest tutorial, ați învățat cum să utilizați modulul de threading încorporat al lui Python pentru a implementa multithreading. Iată un rezumat al principalelor concluzii:

  • Constructorul Thread poate fi folosit pentru a crea un obiect thread. Folosind threading.Thread(target=,args=()) creează un fir care rulează ținta apelabilă cu argumentele specificate în args.
  • Programul Python rulează pe un fir principal, astfel încât obiectele thread pe care le creați sunt fire suplimentare. Puteți apela funcția active_count() returnează numărul de fire active la orice instanță.
  • Puteți începe un fir folosind metoda start() pe obiectul thread și puteți aștepta până când se termină execuția folosind metoda join().

Puteți codifica exemple suplimentare modificând timpii de așteptare, încercând o operație I/O diferită și multe altele. Asigurați-vă că implementați multithreading în viitoarele proiecte Python. Codare fericită!🎉