11.1 Global Interpreter Lock
Global Interpreter Lock (GIL)
est un mécanisme dans
l'interpréteur CPython qui garantit qu'un seul thread
exécute le bytecode Python à la fois. Le GIL
empêche
l'exécution parallèle des threads, ce qui peut avoir un impact
négatif sur les performances des programmes multi-threads,
surtout sur les processeurs multi-cœurs.
Raisons de l'existence du GIL
Simplification de la gestion de la mémoire : le GIL
simplifie la gestion de la mémoire et le garbage collection,
rendant Python plus facile à implémenter.
Sécurité des threads : le GIL
prévient
les conditions de compétition, rendant l'exécution du code
sécurisée pour les threads sans avoir besoin d'utiliser
des verrous.
Problèmes causés par le GIL
Performances limitées : les programmes multi-threads
effectuant des tâches intensives en calcul ne peuvent
pas tirer pleinement parti des processeurs multi-cœurs
à cause des limitations du GIL
.
Détérioration des performances : les programmes qui utilisent intensivement les threads pour effectuer des tâches peuvent rencontrer une dégradation des performances à cause des basculements de contexte entre threads.
Actuellement, il y a 4 principales façons de « contourner le GIL » :
11.2 Utilisation du multiprocessus (multiprocessing)
Le module multiprocessing
permet de créer
des processus qui s'exécutent en parallèle et ne sont
pas limités par le GIL
, car chaque processus
a son propre interpréteur Python et sa mémoire.
Exemple :
import multiprocessing
def worker(num):
print(f'Worker: {num}')
def main():
processes = []
for i in range(5):
p = multiprocessing.Process(target=worker, args=(i,))
processes.append(p)
p.start()
for p in processes:
p.join()
main()
11.3 Programmation Asynchrone
La programmation asynchrone utilisant
asyncio
permet d'exécuter des tâches en parallèle
sans bloquer le thread principal. Même si cela ne contourne
pas le GIL
au sens propre, cela permet d'utiliser
efficacement le temps d'attente pour effectuer d'autres tâches.
Exemple :
import asyncio
async def main():
await asyncio.sleep(1)
print('Hello')
asyncio.run(main())
11.4 Utilisation de bibliothèques avec gestion des threads propre
Certaines bibliothèques, comme NumPy et SciPy, sont écrites en C et utilisent leurs propres mécanismes de gestion des threads, ce qui leur permet de contourner le GIL et d'utiliser efficacement les processeurs multi-cœurs pour les calculs.
En fait, c'est l'une des principales raisons du succès de Python, aussi lent soit-il. Tous les calculs complexes sont réécrits en C/C++ et exécutés sur tous les cœurs du processeur ou même directement sur les cœurs du GPU. Et sur les GPU modernes, il y a des milliers de cœurs.
Ainsi, il n'est plus important de savoir si le langage est rapide ou lent, tant que tous les calculs gourmands en ressources sont effectués par des bibliothèques externes ou complètement délocalisés dans des centres de données à distance.
11.5 Exécution de calculs hors de l'interpréteur Python
L'utilisation d'extensions en C/C++ ou d'autres langages qui peuvent effectuer des calculs hors de l'interpréteur Python puis retourner le résultat. Cela permet d'éviter le blocage du GIL pendant l'exécution de calculs intensifs.
Exemple d'utilisation de Cython :
# example.pyx
def compute(int n):
cdef int i, result = 0
for i in range(n):
result += i
return result
Compilation et utilisation :
cythonize -i example.pyx
import example
print(example.compute(1000000))
GO TO FULL VERSION