În dezvoltarea de aplicații de producție (backend-uri web, crawlere de date, procesare de fișiere media sau calcul distribuit), alegerea modelului corect de concurență în Python este diferența dintre o aplicație care răspunde în 10 milisecunde și una care blochează complet serverul. Datorită prezenței Global Interpreter Lock (GIL) în interpretorul standard CPython, nu toate sarcinile paralele pot fi accelerate prin aceleași mecanisme.
| Natură Sarcină | Soluție Tehnologică | Mecanism de Funcționare | Exemple Reale |
|---|---|---|---|
| I/O-Bound Asincron | asyncio | Single-threaded, cooperativ bazat pe Event Loop. Consum minim de memorie RAM. | Microservicii FastAPI, zeci de mii de conexiuni WebSocket, apeluri HTTP concurente cu httpx. |
| I/O-Bound Blocant / Legacy | threading / asyncio.to_thread |
Thread-uri native de sistem de operare. GIL-ul este eliberat automat în timpul operațiilor I/O. | Interogări la baze de date cu drivere sincrone (ex: psycopg2), citire de fișiere mari de pe disc. |
| CPU-Bound | multiprocessing | Procese complet separate ale sistemului de operare, fiecare cu propriul spațiu de memorie și propriul său GIL. | Calcul numeric, compresie video, antrenare și inferență de modele AI, procesare masivă de imagini. |
1. Executarea Codului Sincron Blocant în Asyncio (asyncio.to_thread)
Dacă scrieți o aplicație asincronă (de exemplu un endpoint FastAPI) și trebuie să apelați o funcție care face I/O sincron blocant sau calcule CPU medii, apelarea ei directă va îngheța complet Event Loop-ul pentru toți ceilalți clienți conectați! Soluția elegantă din Python 3.9+ este asyncio.to_thread:
import asyncio
import time
def operatie_sincrona_grea(fisier: str) -> int:
# Simulare citire sincronă sau procesare blocantă
time.sleep(2.0)
return 42
async def handler_cerere_web():
print("Încep procesarea asincronă...")
# Execută funcția sincronă într-un thread pool dedicat fără a bloca Event Loop-ul:
rezultat = await asyncio.to_thread(operatie_sincrona_grea, "date.bin")
print(f"Rezultat obținut: {rezultat}")
asyncio.run(handler_cerere_web())
2. Paralelism Real pe Toate Nucleele CPU cu ProcessPoolExecutor
Pentru calcule intensive din punct de vedere procesor (CPU-bound), thread-urile nu ajută din cauza GIL-ului. Folosim concurrent.futures.ProcessPoolExecutor pentru a distribui calculul pe toate nucleele CPU:
from concurrent.futures import ProcessPoolExecutor
import os
import math
def calculeaza_factori(numar: int) -> int:
# Calcule CPU-bound intense
return sum(math.isqrt(i) for i in range(1, numar))
def main():
numere = [15_000_000, 18_000_000, 20_000_000, 22_000_000]
# Lansează un număr de procese worker egal cu numărul de nuclee CPU:
with ProcessPoolExecutor() as executor:
# map distribuie automat lista pe procese și colectează rezultatele în ordine:
rezultate = list(executor.map(calculeaza_factori, numere))
print("Toate rezultatele au fost calculate în paralel:", rezultate)
if __name__ == '__main__':
# Clauza '__main__' este OBLIGATORIE în multiprocessing pentru a preveni bucle infinite de fork!
main()
Noutatea Majoră: Free-Threaded Python 3.13 (PEP 703)
Începând cu Python 3.13, interpretorul CPython oferă un build experimental fără GIL (compilat cu opțiunea --disable-gil). În acest mod, thread-urile standard din modulul threading pot rula bytecode Python cu paralelism adevărat pe mai multe nuclee fizice, marcând începutul unei noi ere pentru performanța limbajului Python în scenarii CPU-bound.