Concurrence Python : GIL, asyncio, multiprocessing
Le GIL en une phrase
Le Global Interpreter Lock de CPython n'autorise qu'un seul thread à exécuter du bytecode Python à la fois. Tes threads Python sont donc parfaits pour attendre (I/O), inutiles pour calculer (CPU).
| Charge | Bon outil Python | Équivalent dans ta vie backend |
|---|---|---|
| I/O (API, DB, fichiers) | asyncio / threads | event loop Node, virtual threads |
| Calcul pur Python | multiprocessing | plusieurs workers/process |
| Calcul numérique | numpy/PyTorch (le GIL est relâché !) | JNI/FFI vers du natif |
Le secret : le ML ne calcule jamais « en Python »
numpy, PyTorch, scikit-learn : le gros du calcul s'exécute en C/CUDA, et ces bibliothèques relâchent le GIL pendant. Python n'est que le chef d'orchestre — les musiciens sont natifs. C'est pour ça qu'un serveur d'inférence Python tient la charge : pendant que le GPU calcule, le GIL est libre pour accepter d'autres requêtes.
Les patterns concrets côté serving
- FastAPI + endpoints
async: pendant l'attente GPU/réseau, l'event loop sert d'autres requêtes — ton réflexe asyncio s'applique tel quel ; - workers multiples (uvicorn/gunicorn) : plusieurs process pour utiliser plusieurs cœurs — attention, chaque worker charge sa copie du modèle (RAM/VRAM × N !) ;
- le modèle dans un process dédié (pattern vLLM/Triton) : l'API n'est qu'un proxy fin vers le moteur d'inférence — sépare le cycle de vie web du cycle de vie modèle.
Et côté data : DataLoader(num_workers=N) de PyTorch utilise du multiprocessing pour préparer les batches pendant que le GPU s'entraîne — le producteur/consommateur classique.
Deux threads Python qui font du calcul pur s'exécutent…