Titre : Introduction aux threads en Python : threading vs multiprocessing


Introduction

Quand on développe une application Python, il peut être utile d’exécuter plusieurs tâches en même temps. Pour cela, Python propose deux techniques principales : les threads (avec le module threading) et les processus (avec multiprocessing). Les threads permettent d’exécuter plusieurs fonctions simultanément au sein du même processus, partageant la même mémoire, tandis que les processus sont totalement indépendants, chacun avec sa propre mémoire.

Mais alors, quand utiliser l’un ou l’autre ? Et comment créer un thread en Python ? Cet article vous propose une introduction pratique à threading, et une comparaison avec multiprocessing pour bien comprendre les avantages et les limites de chaque approche.


1. Qu’est-ce qu’un thread ?

Un thread (ou fil d’exécution) est une unité d’exécution légère, qui s'exécute en parallèle à d'autres threads dans le même processus. Tous les threads partagent la mémoire et les ressources du programme principal.

Avantages :

  • Partage de la mémoire (communication facile entre threads)
  • Léger et rapide à créer
  • Idéal pour les tâches I/O (réseau, fichiers…)

Inconvénients :

  • GIL (Global Interpreter Lock) empêche les threads Python purs d’être exécutés en vrai parallèle sur plusieurs cœurs pour les tâches CPU.

2. Créer un thread en Python

import threading

def afficher():
    print("Thread exécuté")

thread = threading.Thread(target=afficher)
thread.start()
thread.join()

  • start() lance le thread.
  • join() attend la fin du thread.

3. Lancer plusieurs threads

def travail(n):
    print(f"Thread {n} en cours")

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

for t in threads:
    t.join()

4. Utiliser une classe personnalisée pour les threads

Vous pouvez hériter de la classe Thread pour créer des threads personnalisés :

class MonThread(threading.Thread):
    def __init__(self, nom):
        super().__init__()
        self.nom = nom

    def run(self):
        print(f"Thread {self.nom} est lancé")

t1 = MonThread("A")
t2 = MonThread("B")
t1.start()
t2.start()
t1.join()
t2.join()

5. Partage de ressources et synchronisation

Problème classique : plusieurs threads accèdent à une même variable

compteur = 0

def incrementer():
    global compteur
    for _ in range(100000):
        compteur += 1

t1 = threading.Thread(target=incrementer)
t2 = threading.Thread(target=incrementer)
t1.start()
t2.start()
t1.join()
t2.join()
print("Compteur =", compteur)

➡ Résultat incohérent à cause de conditions de course.


6. Utiliser un verrou (Lock) pour synchroniser les threads

verrou = threading.Lock()
compteur = 0

def incrementer():
    global compteur
    for _ in range(100000):
        with verrou:
            compteur += 1

t1 = threading.Thread(target=incrementer)
t2 = threading.Thread(target=incrementer)
t1.start()
t2.start()
t1.join()
t2.join()
print("Compteur synchronisé =", compteur)

7. Différences entre threading et multiprocessing

AspectThreadingMultiprocessing
TypePlusieurs threads dans 1 processusPlusieurs processus séparés
Partage mémoireOuiNon
Véritable parallèleNon (à cause du GIL)Oui
Usage recommandéI/O bound (réseau, fichiers...)CPU bound (calculs lourds)
CommunicationSimple (même mémoire)Complexe (via Queue, Pipe, etc.)

8. Quand utiliser threading ?

Utilisez threading lorsque :

  • Vous traitez plusieurs tâches d'attente (I/O)
  • Vous avez besoin de réactivité (ex. interfaces graphiques)
  • Vous n’avez pas besoin de calculs lourds

9. Exemples concrets d'utilisation de threading

Téléchargement de plusieurs fichiers en parallèle :

import requests

def telecharger(url):
    print(f"Téléchargement de {url}")
    r = requests.get(url)
    print(f"{url} terminé ({len(r.content)} octets)")

urls = ["https://www.example.com", "https://www.python.org"]

threads = [threading.Thread(target=telecharger, args=(u,)) for u in urls]
for t in threads: t.start()
for t in threads: t.join()

10. Limites de threading en Python

  • Le GIL empêche l'exécution simultanée de plusieurs threads sur plusieurs cœurs pour les calculs purs.
  • Attention aux bugs difficiles à reproduire à cause des accès concurrents.
  • Privilégiez multiprocessing pour le traitement intensif.

Conclusion

Le module threading de Python est un outil puissant pour lancer plusieurs tâches concurrentes dans le même processus. Bien qu’il ne permette pas le vrai parallélisme CPU à cause du GIL, il reste très efficace pour les tâches d’entrées/sorties, comme le téléchargement, les appels API ou la gestion d’interfaces utilisateurs.

Savoir quand utiliser threading ou multiprocessing est essentiel pour écrire des programmes performants et bien structurés.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *