Fabrication industrielle
Internet des objets industriel | Matériaux industriels | Entretien et réparation d'équipement | Programmation industrielle |
home  MfgRobots >> Fabrication industrielle >  >> Industrial programming >> Python

Maîtriser le Multithreading Python : GIL Expliqué avec Exemples Pratiques

Python offre à la fois multiprocessing et multithreading pour accélérer les charges de travail. Ce guide vous accompagne dans la création d'applications robustes basées sur les threads, les nuances du Global Interpreter Lock (GIL), et comment éviter les pièges courants comme les deadlocks et race conditions.

Qu'est-ce qu'un Thread ?

Un thread est une unité d'exécution légère au sein d'un seul processus. Les threads partagent l'espace mémoire du processus, permettant un échange de données efficace, tandis que le système d'exploitation les planifie pour s'exécuter concurremment sur un ou plusieurs cœurs CPU.

Qu'est-ce qu'un Processus ?

Un processus est une instance de programme indépendante. Quand vous lancez une application — disons un navigateur ou éditeur de texte — l'OS crée un processus séparé avec son propre code, données, et map mémoire.

Fondamentaux du Multithreading Python

Le module threading de Python fournit une API haut niveau pour gérer les threads, alors que le module legacy _thread est déprécié et seulement conservé pour compatibilité ascendante. Les threads sont idéaux pour tâches I/O-bound, tandis que le travail CPU-bound devrait utiliser le module multiprocessing pour contourner le GIL.

Quand Utiliser le Multithreading

Le multithreading brille quand vous devez effectuer plusieurs opérations I/O-lourdes — requêtes réseau, I/O fichiers, ou appels base de données — simultanément. Des threads bien conçus peuvent améliorer réactivité et débit sans la surcharge de spawn de nouveaux processus.

Modules Python Clés pour le Threading

  1. _thread — Bas niveau, déprécié.
  2. threading — Moderne, riche en fonctionnalités, et choix recommandé.

Utilisation du Module threading

Le module threading offre une classe Thread, primitives de synchronisation (Lock, RLock, Semaphore, Condition, Event, Barrier), et fonctions utilitaires comme activeCount() et enumerate(). Voici un exemple minimal démontrant création et synchronisation de threads.

import time
import _thread
def thread_test(name, wait):
    for i in range(4):
        time.sleep(wait)
        print(f"Running {name}")
    print(f"{name} a terminé son exécution")
if __name__ == "__main__":
    _thread.start_new_thread(thread_test, ("First Thread", 1))
    _thread.start_new_thread(thread_test, ("Second Thread", 2))
    _thread.start_new_thread(thread_test, ("Third Thread", 3))

L'exécution de ce code produit une sortie entrelacée démontrant l'exécution concurrente.

Maîtriser le Multithreading Python : GIL Expliqué avec Exemples Pratiques

Explication

  1. Importer time et le module legacy _thread.
  2. Définir thread_test pour effectuer travail et logger progression.
  3. Lancer trois threads indépendants via start_new_thread.

Threading Moderne avec threading.Thread

En pratique, vous sous-classerez threading.Thread et surchargerez sa méthode run(). L'exemple suivant miroite le comportement précédent mais utilise l'API haut niveau.

import time
import threading
class ThreadTester(threading.Thread):
    def __init__(self, name, delay):
        super().__init__()
        self.name = name
        self.delay = delay
    def run(self):
        for _ in range(4):
            time.sleep(self.delay)
            print(f"Running {self.name}")
        print(f"{self.name} a terminé son exécution")
if __name__ == "__main__":
    threads = [
        ThreadTester("First Thread", 1),
        ThreadTester("Second Thread", 2),
        ThreadTester("Third Thread", 3),
    ]
    for t in threads:
        t.start()
    for t in threads:
        t.join()

Sortie :

Maîtriser le Multithreading Python : GIL Expliqué avec Exemples Pratiques

Explication

  1. Sous-classer Thread pour encapsuler comportement thread.
  2. Surcharger __init__ pour stocker données thread-spécifiques.
  3. Surcharger run pour définir la tâche.
  4. Instancier, démarrer, et joindre chaque thread.

Pièges Courants du Threading

Deadlocks

Un deadlock se produit quand deux ou plus threads attendent indéfiniment des ressources détenues par les autres. Illustration classique : le problème des Philosophes Dînant — cinq philosophes compétitionnent pour cinq fourchettes, chacun en ayant besoin de deux pour manger. Si chaque philosophe prend la fourchette de droite d'abord, une attente circulaire surgit, stoppant tout progrès.

Race Conditions

Quand plusieurs threads modifient des données partagées sans coordination appropriée, l'état final peut être imprévisible. Par exemple :

i = 0
for _ in range(100):
    print(i)
    i += 1

Lancer cette boucle en threads parallèles peut produire une séquence brouillée car les incréments se chevauchent.

Synchroniser les Threads

Le module threading fournit plusieurs primitives de synchronisation. La plus fondamentale est Lock, qui assure un accès exclusif à une ressource partagée.

import threading
lock = threading.Lock()
def worker(name):
    for _ in range(5):
        lock.acquire()
        print(f"{name} a acquis le lock")
        # section critique
        lock.release()
if __name__ == "__main__":
    t1 = threading.Thread(target=worker, args=("Thread A",))
    t2 = threading.Thread(target=worker, args=("Thread B",))
    t1.start(); t2.start()
    t1.join(); t2.join()

La sortie démontre l'accès sérialisé à la section critique.

Maîtriser le Multithreading Python : GIL Expliqué avec Exemples Pratiques

Autres Outils de Synchronisation

Comprendre le Global Interpreter Lock (GIL)

CPython utilise le GIL pour sérialiser l'exécution du bytecode à travers les threads. Bien que cela protège les structures internes de l'interpréteur (ex: comptage de références) des race conditions, cela limite aussi le parallélisme pour tâches CPU-bound sur systèmes multi-cœurs.

  1. Quand un thread démarre, il acquiert le GIL.
  2. Si un autre thread a besoin du GIL, il bloque jusqu'à ce que le premier le libère.
  3. Les threads I/O-bound libèrent le GIL pendant appels bloquants, permettant aux autres de s'exécuter.
  4. Les threads CPU-bound gardent le GIL, empêchant autres threads d'exécuter du code Python.

Par conséquent, le multithreading en Python apporte peu de bénéfice pour charges de calcul intensives ; multiprocessing est l'approche préférée dans ces cas.

Pourquoi le GIL Existe

Le ramasse-miettes CPython repose sur le comptage de références. Sans verrou global, mises à jour concurrentes des comptes de références mèneraient à race conditions et corruption mémoire. Un verrou par objet introduirait une surcharge significative, donc le GIL a été adopté comme solution simple et efficace.

Points Pratiques à Retenir

Python

  1. Maîtriser la fonction free() en C : Guide pratique et exemple
  2. Maîtriser la méthode strip() de Python : Guide complet et exemples pratiques
  3. Maîtriser str.count() de Python : Compter caractères et sous-chaînes avec exemples
  4. Fonction Python round() expliquée avec exemples pratiques
  5. Maîtriser la fonction Python map() : syntaxe, exemples et bonnes pratiques
  6. Python timeit() : mesurer le temps d'exécution avec exemples pratiques
  7. Python Counter dans collections : comptage efficace, mise à jour et opérations arithmétiques
  8. Python list.count() : Guide Expert avec Exemples Pratiques
  9. Python list.index() : Comment Trouver les Positions d'Éléments avec Exemples Pratiques