Obiective:
- Introducere în biblioteca
threading - Crearea și gestionarea firelor de execuție
- Comunicarea între firele de execuție
- Sincronizarea firelor de execuție
- Gestionarea resurselor partajate
- Exemple practice și aplicații ale bibliotecii
threading
Introducere în biblioteca threading
Biblioteca threading din Python furnizează o modalitate de a crea și gestiona fire de execuție. Aceasta permite rularea paralelă a mai multor sarcini în cadrul unui singur proces. Firele de execuție au avantajul de a partaja spațiul de adresă al procesului și au un consum redus de resurse în comparație cu procesele separate. Cu toate acestea, din cauza Global Interpreter Lock (GIL) din Python, firele de execuție nu sunt întotdeauna eficiente în cazul sarcinilor care necesită calcul intensiv.
Crearea și gestionarea firelor de execuție
Pentru a crea un fir de execuție, putem defini o funcție care va fi executată de către firul de execuție și apoi să inițializăm și să pornim un obiect Thread:
import threading
def afiseaza_mesaj(mesaj):
print(f'Firul de execuție: {threading.current_thread().name} - {mesaj}')
def main():
thread1 = threading.Thread(target=afiseaza_mesaj, args=('Salut!',), name='Thread1')
thread2 = threading.Thread(target=afiseaza_mesaj, args=('Bună ziua!',), name='Thread2')
thread1.start()
thread2.start()
thread1.join()
thread2.join()
main()
În acest exemplu, funcția afiseaza_mesaj va fi executată de către două fire de execuție diferite. Funcția start pornește firele de execuție, iar funcția join așteaptă ca firele de execuție să se termine înainte de a continua execuția.
Comunicarea între firele de execuție
Comunicarea între firele de execuție este importantă atunci când acestea trebuie să partajeze date sau să sincronizeze execuția. Biblioteca threading oferă diferite metode de comunicare între firele de execuție, cum ar fi:
- Variabilele partajate: variabilele pot fi partajate între firele de execuție, deoarece acestea se execută în cadrul aceluiași proces și au același spațiu de adresă.
Queue: o coadă care permite firelor de execuție să comunice prin trimiterea și primirea de mesaje.
Variabile partajate
Următorul exemplu ilustrează utilizarea unei variabile partajate pentru a comunica între firele de execuție:
import threading
def modifica_variabila(variabila, valoare):
variabila.append(valoare)
print(f'Variabila modificată: {variabila}')
def main():
variabila = []
thread1 = threading.Thread(target=modifica_variabila, args=(variabila, 1), name='Thread1')
thread2 = threading.Thread(target=modifica_variabila, args=(variabila, 2), name='Thread2')
thread1.start()
thread2.start()
thread1.join()
thread2.join()
main()
Queue
Următorul exemplu ilustrează utilizarea unei cozi pentru a comunica între firele de execuție:
import threading
import queue
def produce_elemente(coada):
for i in range(5):
coada.put(i)
print(f'Element adăugat: {i}')
def consuma_elemente(coada):
while not coada.empty():
element = coada.get()
print(f'Element extras: {element}')
def main():
coada = queue.Queue()
thread_producator =threading.Thread(target=produce_elemente, args=(coada,), name='Producător')
thread_consumator = threading.Thread(target=consuma_elemente, args=(coada,), name='Consumator')
thread_producator.start()
thread_consumator.start()
thread_producator.join()
thread_consumator.join()
main()
Sincronizarea firelor de execuție
Sincronizarea firelor de execuție este esențială pentru a evita condițiile de concurență și pentru a asigura o execuție corectă a programului. Biblioteca threading oferă diferite mecanisme de sincronizare, cum ar fi:
- Locks: o metodă de a asigura accesul exclusiv la o resursă partajată.
- Semaphores: o metodă de control al accesului simultan la o resursă partajată.
- Conditions: o metodă de a sincroniza comportamentul a două sau mai multe fire de execuție.
Locks
Următorul exemplu ilustrează utilizarea unui lock pentru a asigura accesul exclusiv la o resursă partajată:
import threading
def adauga_elemente(lista, lock):
for i in range(5):
with lock:
lista.append(i)
print(f'Element adăugat: {i}')
def main():
lista = []
lock = threading.Lock()
thread1 = threading.Thread(target=adauga_elemente, args=(lista, lock), name='Thread1')
thread2 = threading.Thread(target=adauga_elemente, args=(lista, lock), name='Thread2')
thread1.start()
thread2.start()
thread1.join()
thread2.join()
main()
Semaphores
Următorul exemplu ilustrează utilizarea unui semafor pentru a controla accesul simultan la o resursă partajată:
import threading
import time
def acceseaza_resursa(semafor):
with semafor:
print(f'Resursa accesată de: {threading.current_thread().name}')
time.sleep(1)
def main():
semafor = threading.Semaphore(2)
threads = [threading.Thread(target=acceseaza_resursa, args=(semafor,), name=f'Thread{i}') for i in range(5)]
for thread in threads:
thread.start()
for thread in threads:
thread.join()
main()
Conditions
Următorul exemplu ilustrează utilizarea unei condiții pentru a sincroniza comportamentul a două fire de execuție:
import threading
def produc_elemente(lista, conditie):
for i in range(5):
with conditie:
lista.append(i)
print(f'Element adăugat: {i}')
conditie.notify()
def consuma_elemente(lista, conditie):
for _ in range(5):
with conditie:
conditie.wait()
element = lista.pop(0)
print(f'Element extras: {element}')
def main():
lista = []
conditie = threading.Condition()
thread_producator = threading.Thread(target=produc_elemente, args=(lista, conditie), name='Producător')
thread_consumator = threading.Thread(target=consuma_elemente, args=(lista, conditie), name='Consumator')
thread_producator.start()
thread_consumator.start()
thread_producator.join()
thread_consumator.join()
main()
Exemple practice și aplicații ale bibliotecii threading
Biblioteca threading poate fi utilizată pentru a implementa diverse aplicații și funcționalități, cum ar fi:
- Servere web cu fire de execuție multiple pentru a gestiona cererile simultan.
- Implementarea de algoritmi paraleli pentru a îmbunătăți performanța.
- Realizarea de aplicații cu interfață grafică care necesită actualizări în timp real.
Un exemplu de aplicație care utilizează biblioteca threading ar putea fi un server web simplu care acceptă conexiuni simultane:
import socket
import threading
def gestioneaza_conexiune(client_socket, client_address):
print(f'Conexiune acceptată de la: {client_address}')
request_data = client_socket.recv(1024)
print(f'Date primite: {request_data}')
response = b'HTTP/1.1 200 OKrnContent-Type: text/htmlrnrn<b>Salut!</b>'
client_socket.sendall(response)
client_socket.close()
def main():
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.bind(('localhost', 8080))
server_socket.listen(5)
print('Serverul a început să asculte pe portul 8080...')
try:
while True:
client_socket, client_address = server_socket.accept()
thread = threading.Thread(
target=gestioneaza_conexiune,
args=(client_socket, client_address),
name=f'Thread-{client_address}'
)
thread.start()
except KeyboardInterrupt:
print('nServerul s-a oprit.')
finally:
server_socket.close()
main()
În acest exemplu, funcția gestioneaza_conexiune este responsabilă de gestionarea conexiunilor cu clienții. Funcția acceptă un socket client și adresa clientului ca argumente. Serverul primește date de la client și trimite înapoi un răspuns HTTP simplu.
Funcția principală main creează un socket server, îl leagă de adresa ‘localhost’ și portul 8080, apoi începe să asculte pentru conexiuni. Pentru fiecare conexiune acceptată, serverul creează un nou fir de execuție care va gestiona conexiunea cu clientul.
Acest exemplu ilustrează cum biblioteca threading poate fi folosită pentru a crea un server web care acceptă și gestionează simultan mai multe conexiuni.