Module threading de Python

de PythonthreadingLe module est l'une des bibliothèques standard utilisées pour implémenter la programmation multithread. Le multithreading permet à un programme d'exécuter plusieurs tâches en même temps, améliorant ainsi l'efficacité et la réactivité du programme.

threadingLe module fournit des outils pour créer et gérer des threads, permettant aux développeurs d'écrire facilement des programmes concurrents.

Qu'est-ce qu'un thread ?

Vous pouvez considérer les threads comme des employés dans un bureau :

  • Un programme monothread est comme n'avoir qu'un seul employé, qui doit accomplir séquentiellement toutes les tâches telles que imprimer des documents, répondre aux e-mails et préparer du café.

  • Un programme multithread est comme avoir plusieurs employés, qui peuventsimultanémenteffectuer différentes tâches, améliorant grandement l'efficacité du travail.

En informatique :

  • Processus: Un programme en cours d'exécution avec un espace mémoire indépendant (par exemple, le navigateur et le lecteur de musique que vous avez ouverts en même temps sont deux processus).
  • Thread: Un flux d'exécution indépendant au sein d'un processus, et l'unité de base de l'ordonnancement du CPU. Tous les threads d'un même processuspartagent l'espace mémoire du processus(comme les variables globales).

Pourquoi utiliser le multithreading ?

Dans un programme monothread, les tâches sont exécutées l'une après l'autre. Si une tâche doit attendre (par exemple, attendre une réponse réseau ou une lecture de fichier), tout le programme est bloqué jusqu'à ce que la tâche se termine. Le multithreading permet au programme de continuer à exécuter d'autres tâches pendant l'attente d'une tâche, améliorant ainsi les performances globales du programme.

Threads Python et le verrou global de l'interpréteur (GIL)

Python possède un mécanisme appelé le verrou global de l'interpréteur (GIL), qui garantit qu'un seul thread peut exécuter du bytecode Python à un instant donné.

Que signifie ceci ?Pour les tâches gourmandes en CPU (comme le calcul scientifique, le traitement d'images), en raison du GIL, le multithreading ne peut généralement pas tirer parti de plusieurs cœurs pour accélérer le calcul, et peut même devenir plus lent à cause du surcoût de commutation de threads.

Alors, où le multithreading en Python est-il utile ?Pour les tâches gourmandes en E/S (comme les requêtes réseau, la lecture/écriture de fichiers, l'attente de saisie utilisateur), les threads libèrent le GIL pendant qu'ils attendent la fin des opérations d'E/S, ce qui permet aux autres threads de s'exécuter. Cela peut considérablement améliorer la réactivité et l'efficacité globales du programme, car pendant que vous attendez la réponse d'une page web, le programme peut traiter une autre tâche.


Comment utiliser le module threading ?

Pour utiliserthreadingle module, la première étape consiste à l'importer :

import threading
import time  # 用于模拟耗时操作

La façon la plus basique de créer un thread est d'utiliserthreading.Threadla classe.

Explication de la syntaxe :

thread_obj = threading.Thread(target=函数名, args=(参数元组,))
  • target: Spécifie la fonction à exécuter après le démarrage du thread.
  • args: L'argument passé à la fonction cible doit être un tuple. S'il n'y a qu'un seul argument, il doit être écrit sous(parameter,)la forme `(arg,)`.

1. Création de threads

En Python, vous pouvez, en héritant de lathreading.Threadclasse ou en utilisant directement lethreading.Threadconstructeur, créer un thread.

Méthode 1 : Héritagethreading.ThreadClasse

Exemple

import threading

class MyThread(threading.Thread):
    def run(self):
        print("Le thread commence à s'exécuter")
        # Écrire le code à exécuter par le thread ici
        print("Exécution du thread terminée")

# Créer une instance de thread
thread = MyThread()
# Démarrer le thread
thread.start()
# Attendre que le thread se termine
thread.join()
print("Thread principal terminé")

Méthode 2 : Utilisation dethreading.Threadconstructeur.

Exemple

import threading

def my_function():
    print("Le thread commence à s'exécuter")
    # Écrire le code à exécuter par le thread ici
    print("Exécution du thread terminée")

# Créer une instance de thread
thread = threading.Thread(target=my_function)
# Démarrer le thread
thread.start()
# Attendre que le thread se termine
thread.join()
print("Thread principal terminé")

2. Synchronisation des threads

Dans la programmation multithread, plusieurs threads peuvent accéder simultanément à des ressources partagées, ce qui peut entraîner des problèmes d'incohérence des données. Pour éviter cela, des mécanismes de synchronisation des threads tels que les verrous (Lock)。

Exemple

import threading

# Créer un objet verrou
lock = threading.Lock()

def my_function():
    with lock:
        print("Le thread commence à s'exécuter")
        # Écrire le code à exécuter par le thread ici
        print("Exécution du thread terminée")

# Créer une instance de thread
thread1 = threading.Thread(target=my_function)
thread2 = threading.Thread(target=my_function)
# Démarrer le thread
thread1.start()
thread2.start()
# Attendre que le thread se termine
thread1.join()
thread2.join()
print("Thread principal terminé")

3. Communication entre threads

La communication entre threads peut être implémentée via une file d'attente (Queue).Queueest sûr pour les threads et peut transmettre des données en toute sécurité entre plusieurs threads.

Exemple

import threading
import queue

def worker(q):
    while not q.empty():
        item = q.get()
        print(f"Traitement de l'élément : {item}")
        q.task_done()

# Créer une file d'attente et la remplir avec des données
q = queue.Queue()
for i in range(10):
    q.put(i)

# Créer des instances de thread
thread1 = threading.Thread(target=worker, args=(q,))
thread2 = threading.Thread(target=worker, args=(q,))
# Démarrer les threads
thread1.start()
thread2.start()
# Attendre que tous les éléments de la file d'attente soient traités
q.join()
print("Tous les éléments traités")

Classes, méthodes et attributs courants

1. Classes principales

Classe/Méthode/AttributDescriptionExemple
threading.ThreadClasse Thread, utilisée pour créer et gérer des threadst = Thread(target=func, args=(1,))
threading.LockVerrou mutex (verrou primitif)lock = Lock()
threading.RLockVerrou réentrant (peut être acquis plusieurs fois par le même thread)rlock = RLock()
threading.EventObjet Event, utilisé pour la synchronisation des threadsevent = Event()
threading.ConditionVariable de condition, utilisée pour une coordination complexe des threadscond = Condition()
threading.SemaphoreSémaphore, contrôle le nombre de threads simultanéssem = Semaphore(3)
threading.BoundedSemaphoreSémaphore borné (empêche le compteur de dépasser la valeur initiale)b_sem = BoundedSemaphore(2)
threading.TimerThread minuteur, exécution différéetimer = Timer(5.0, func)
threading.localDonnées locales au thread (stockées indépendamment pour chaque thread)local_data = threading.local()

2. Méthodes/attributs courants des objets Thread

Méthode/AttributDescriptionExemple
start()Démarrer le threadt.start()
run()Méthode exécutée par le thread (peut être redéfinie)Redéfinissez cette méthode lors de la définition d'une classe personnalisée
join(timeout=None)Bloque le thread courant jusqu'à ce que le thread cible se terminet.join()
is_alive()Vérifie si le thread est en cours d'exécutionif t.is_alive():
nameNom du thread (peut être modifié)t.name = "Worker-1"
daemonIndicateur de thread démon (se termine automatiquement lorsque le thread principal se termine)t.daemon = True
identIdentifiant du thread (`None` avant le démarrage du thread)None)print(t.ident)

3. Méthodes courantes de Lock/RLock

MéthodeDescriptionExemple
acquire(blocking=True, timeout=-1)Acquérir le verrou (bloquant ou non bloquant)lock.acquire()
release()Libérer le verroulock.release()
locked()Vérifier si le verrou est détenuif not lock.locked():

4. Méthodes courantes de Event

MéthodeDescriptionExemple
set()Définir l'événement sur true et réveiller tous les threads en attenteevent.set()
clear()Réinitialiser l'événement sur falseevent.clear()
wait(timeout=None)Bloquer jusqu'à ce que l'événement soit true ou qu'un délai expireevent.wait(2.0)
is_set()Vérifier l'état de l'événementif event.is_set():

5. Méthodes courantes de Condition

MéthodeDescriptionExemple
wait(timeout=None)Libérer le verrou et bloquer jusqu'à être notifié ou qu'un délai expirecond.wait()
notify(n=1)Réveiller au plusnthreads en attentecond.notify(2)
notify_all()Réveiller tous les threads en attentecond.notify_all()

6. Fonctions/attributs au niveau du module

Fonction/AttributDescriptionExemple
threading.active_count()Retourne le nombre de threads actuellement actifsprint(threading.active_count())
threading.current_thread()Retourne l’objet du thread actuelprint(threading.current_thread().name)
threading.enumerate()Retourne une liste de tous les threads actifsfor t in threading.enumerate():
threading.main_thread()Retourne l’objet du thread principalif threading.current_thread() is threading.main_thread():
threading.get_ident()Retourne l’identifiant du thread actuel (Python 3.3+)print(threading.get_ident())

Exemple

1. Création de threads de base

Exemple

import threading

def worker(num):
    print(f"Worker {num} started")

threads = []
for i in range(3):
    t = threading.Thread(target=worker, args=(i,))
    threads.append(t)
    t.start()

for t in threads:
    t.join()

2. Utilisation de verrous pour protéger les ressources partagées

Exemple

lock = threading.Lock()
count = 0

def increment():
    global count
    with lock:  # Acquiert et libère automatiquement le verrou
        count += 1

threads = [threading.Thread(target=increment) for _ in range(10)]
for t in threads:
    t.start()
for t in threads:
    t.join()
print(count)  # Sortie : 10

3. Synchronisation d'événements

Exemple

event = threading.Event()

def waiter():
    print("En attente de l'événement...")
    event.wait()
    print("Événement déclenché !")

t = threading.Thread(target=waiter)
t.start()

# Le thread principal déclenche l'événement
threading.Event().wait(2.0)  # Délai simulé
event.set()
t.join()

4. Modèle producteur-consommateur (Condition)

Exemple

import random
from threading import Condition

queue = []
cond = Condition()
MAX_ITEMS = 5

def producer():
    for _ in range(10):
        with cond:
            while len(queue) >= MAX_ITEMS:
                cond.wait()
            item = random.randint(1, 100)
            queue.append(item)
            print(f"Produced {item}")
            cond.notify()

def consumer():
    for _ in range(10):
        with cond:
            while not queue:
                cond.wait()
            item = queue.pop(0)
            print(f"Consumed {item}")
            cond.notify()

threading.Thread(target=producer).start()
threading.Thread(target=consumer).start()

Notes

  1. Verrou global de l'interpréteur (GIL): Le GIL de Python ne permet qu'à un seul thread d'exécuter du bytecode Python à la fois. Par conséquent, dans les tâches gourmandes en CPU, le multithreading peut ne pas apporter d'améliorations de performances. Pour les tâches gourmandes en E/S, le multithreading reste bénéfique.

  2. Sécurité des threads: Dans un environnement multithread, assurez-vous que l'accès aux ressources partagées est thread-safe, en évitant les courses de données et les interblocages.

  3. Nombre de threads: Créer trop de threads peut épuiser les ressources système et affecter les performances du programme. Contrôlez raisonnablement le nombre de threads, ou utilisez un pool de threads (ThreadPoolExecutor) pour gérer les threads.


Quiz

Quiz de connaissances sur le module Threading

Question 1
1. Le Global Interpreter Lock (GIL) de Python affecte principalement les performances de quel type de tâche multithread ?
Question 2
2. Après avoir créé un objet thread, quelle méthode doit être appelée pour démarrer l'exécution du thread ?
Question 3
3. Lorsque plusieurs threads doivent modifier la même variable globale, que faut-il utiliser pour éviter la corruption des données ?
Question 4
4. À quoi sert l'utilisation de thread.join() par le thread principal ?
Autres extensions