Ghid de Concurență în Python: Asyncio, Threading și Multiprocessing în Python

Programare asincronă Python 3.10+

Ghid complet de concurență în Python: când alegem asyncio (I/O asincron), când folosim threading și cum scalăm sarcinile CPU-bound pe toate nucleele procesorului cu multiprocessing.

Sintaxă
asyncio.to_thread(func); ProcessPoolExecutor(max_workers=cpu_count())

Î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.

Matricea de decizie pentru concurență în Python
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:

Descărcarea I/O-ului sincron pe un thread separat
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:

Calcul paralel distribuit pe 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.