Threading Python : une introduction – toptips.fr
Ce guide vous initiera à l'utilisation du module de gestion des threads intégré à Python, explorant ainsi les capacités multithreading de ce langage.
En commençant par les notions de base sur les processus et les threads, vous comprendrez comment le multithreading opère en Python, tout en discernant les concepts de concurrence et de parallélisme. Vous apprendrez ensuite à lancer et à gérer un ou plusieurs threads en Python, grâce au module intégré.
Commençons notre exploration.
Processus contre threads : quelles sont les distinctions ?
Qu'est-ce qu'un processus ?
Un processus représente une instance d'un programme en cours d'exécution.
Cela peut concerner divers éléments, depuis un script Python ou un navigateur web comme Chrome jusqu'à une application de visioconférence. Si vous ouvrez le Gestionnaire des tâches et allez dans l'onglet Performances, puis CPU, vous pourrez observer les processus et les threads qui tournent sur les cœurs de votre processeur.
Comprendre les processus et les threads
En interne, un processus possède une zone mémoire dédiée où sont stockés le code et les données qui lui sont propres.
Un processus est composé d'un ou plusieurs threads. Un thread est la plus petite série d'instructions que le système d'exploitation est en mesure d'exécuter, et il symbolise le flux d'exécution.
Chaque thread dispose de sa propre pile et de ses registres, mais ne possède pas de mémoire dédiée. Tous les threads liés à un processus peuvent accéder aux données de ce dernier. Ainsi, les données et la mémoire sont partagées entre tous les threads d'un même processus.
Sur un processeur avec N cœurs, N processus peuvent s'exécuter simultanément. Cependant, deux threads du même processus ne peuvent pas s'exécuter en parallèle, mais ils peuvent s'exécuter de manière concurrente. Nous allons explorer la notion de concurrence par rapport au parallélisme dans la section suivante.
D'après ce que nous avons appris jusqu'à présent, résumons les différences entre un processus et un thread.
| Fonctionnalité | Processus | Thread |
| Mémoire | Mémoire dédiée | Mémoire partagée |
| Mode d'exécution | Parallèle, concurrent | Concurrent; mais pas parallèle |
| Exécution gérée par | Le système d'exploitation | L'interpréteur CPython |
Multithreading en Python
En Python, le Verrou Global de l'Interpréteur (GIL) assure qu'un seul thread puisse détenir le verrou et s'exécuter à un moment donné. Tous les threads doivent acquérir ce verrou pour pouvoir tourner. Cela garantit qu'un seul thread peut être en cours d'exécution à un instant précis, évitant ainsi le multithreading simultané.
Prenons par exemple deux threads, t1 et t2, appartenant au même processus. Comme les threads partagent les mêmes données, lorsque t1 lit une valeur spécifique k, t2 pourrait modifier cette même valeur k. Cela pourrait engendrer des blocages et des résultats imprévus. Toutefois, seul un des threads peut acquérir le verrou et s'exécuter à un instant donné. Par conséquent, le GIL assure également la sécurité des threads.
Alors, comment obtenir des capacités de multithreading en Python ? Pour bien le comprendre, examinons les concepts de concurrence et de parallélisme.
Concurrence contre parallélisme : un aperçu
Imaginez un processeur avec plusieurs cœurs. Dans l'illustration ci-dessous, le CPU en possède quatre. Cela signifie que nous pouvons avoir quatre opérations différentes exécutées en parallèle à un instant donné.
S'il y a quatre processus, chacun peut s'exécuter de façon autonome et simultanément sur un des quatre cœurs. Imaginons que chaque processus possède deux threads.

Pour comprendre comment fonctionne le threading, passons d'une architecture de processeur multi-cœurs à une architecture monocœur. Comme mentionné précédemment, un seul thread peut être actif à un moment d'exécution donné, mais le cœur du processeur peut alterner entre les threads.

Par exemple, les threads liés aux opérations d'entrée/sortie (E/S) patientent souvent durant ces opérations: que ce soit la saisie par l'utilisateur, les requêtes vers une base de données ou des opérations sur les fichiers. Pendant cette attente, le thread peut libérer le verrou pour qu'un autre thread puisse s'exécuter. Ce temps d'attente peut aussi être une simple action, comme une mise en veille pendant un certain nombre de secondes.
En résumé : lors des opérations d'attente, le thread abandonne le verrou, permettant au cœur du processeur de basculer vers un autre thread. Le thread précédent reprend son exécution une fois l'attente terminée. Ce processus, où le cœur du processeur alterne entre les threads, facilite le multithreading. ✅
Si vous cherchez à implémenter du parallélisme au niveau du processus dans votre application, il serait préférable d'envisager le multiprocessing.
Le module de threading en Python : premiers pas
Python fournit un module de threading qu'il est possible d'importer dans un script Python.
import threading
Pour créer un objet thread en Python, le constructeur Thread est disponible : threading.Thread(…). Voici la syntaxe générale, valable pour la plupart des implémentations de thread :
threading.Thread(target=...,args=...)
Ici,
- target est l'argument-clé qui désigne une fonction Python
- args est le tuple d'arguments que cette fonction cible prend.
Vous aurez besoin de Python 3.x pour exécuter les exemples de code de ce tutoriel. Téléchargez le code et suivez-le.
Comment créer et lancer des threads en Python
Créons un thread qui exécutera une fonction cible.
La fonction cible s'appelle some_func.
import threading
import time
def some_func():
print("Exécution de some_func...")
time.sleep(2)
print("Exécution de some_func terminée.")
thread1 = threading.Thread(target=some_func)
thread1.start()
print(threading.active_count())
Analysons ce que cet extrait de code fait :
- Il importe les modules threading et time.
- La fonction some_func contient des instructions print() qui donnent des informations, et inclut une mise en veille de deux secondes : time.sleep(n) met la fonction en pause pendant n secondes.
- Ensuite, nous définissons un thread thread_1 dont la cible est some_func. threading.Thread(target=…) crée un objet thread.
- Note : Il faut spécifier le nom de la fonction et non un appel de fonction ; utilisez some_func et non some_func().
- La création d'un objet thread ne lance pas le thread lui-même; c'est l'appel à la méthode start() sur l'objet thread qui le fait.
- Pour connaître le nombre de threads actifs, nous utilisons la fonction active_count().
Le script Python s'exécute sur le thread principal, et nous créons un autre thread (thread1) pour exécuter la fonction some_func, de sorte que le nombre de threads actifs est de deux, comme l'indique la sortie :
# Output Exécution de some_func... 2 Exécution de some_func terminée.
En observant de plus près la sortie, nous remarquons qu'au démarrage de thread1, la première instruction d'affichage est exécutée. Mais pendant la mise en veille, le processeur passe au thread principal et affiche le nombre de threads actifs, sans attendre que thread1 termine son exécution.

Attendre la fin d'exécution des threads
Si vous souhaitez que thread1 achève son exécution, vous pouvez appeler la méthode join() après avoir démarré le thread. Cela permettra d'attendre la fin d'exécution du thread1 avant de repasser au thread principal.
import threading
import time
def some_func():
print("Exécution de some_func...")
time.sleep(2)
print("Exécution de some_func terminée.")
thread1 = threading.Thread(target=some_func)
thread1.start()
thread1.join()
print(threading.active_count())
Désormais, thread1 termine son exécution avant que nous affichions le nombre de threads actifs. Seul le thread principal est donc en cours d'exécution, ce qui signifie que le nombre de threads actifs est de un. ✅
# Output Exécution de some_func... Exécution de some_func terminée. 1
Comment lancer plusieurs threads en Python
Créons maintenant deux threads pour exécuter deux fonctions distinctes.
Ici, count_down est une fonction qui prend un nombre comme argument et décompte à partir de ce nombre jusqu'à zéro.
def count_down(n):
for i in range(n,-1,-1):
print(i)
Nous définissons également count_up, une autre fonction Python qui compte à partir de zéro jusqu'à un nombre spécifié.
def count_up(n):
for i in range(n+1):
print(i)
📑 Lorsqu'on utilise la fonction range() avec la syntaxe range(start, stop, step), la limite stop est exclue par défaut.
– Pour décompter à partir d'un nombre spécifique jusqu'à zéro, on utilise une valeur de pas négative de -1 et on définit la limite stop sur -1 afin que zéro soit inclus.
– De la même manière, pour compter jusqu'à n, il faut définir la limite stop sur n + 1. Comme les valeurs par défaut de start et step sont 0 et 1 respectivement, vous pouvez utiliser range(n + 1) pour obtenir la séquence allant de 0 à n.
Ensuite, nous définissons deux threads, thread1 et thread2, pour exécuter respectivement les fonctions count_down et count_up. Nous ajoutons des instructions d'affichage et des mises en veille pour les deux fonctions.
Lors de la création des objets thread, notez que les arguments de la fonction cible doivent être spécifiés sous forme de tuple, dans le paramètre args. Comme les deux fonctions (count_down et count_up) prennent un seul argument, il est nécessaire d'insérer une virgule après la valeur. Ceci assure que l'argument est toujours transmis sous forme de tuple, car dans le cas contraire, il serait considéré comme None.
import threading
import time
def count_down(n):
for i in range(n,-1,-1):
print("Exécution du thread1....")
print(i)
time.sleep(1)
def count_up(n):
for i in range(n+1):
print("Exécution du thread2...")
print(i)
time.sleep(1)
thread1 = threading.Thread(target=count_down,args=(10,))
thread2 = threading.Thread(target=count_up,args=(5,))
thread1.start()
thread2.start()
Dans la sortie :
- La fonction count_up s'exécute sur thread2 et compte de 0 à 5.
- La fonction count_down s'exécute sur thread1 et décompte de 10 à 0.
# Output Exécution du thread1.... 10 Exécution du thread2... 0 Exécution du thread1.... 9 Exécution du thread2... 1 Exécution du thread1.... 8 Exécution du thread2... 2 Exécution du thread1.... 7 Exécution du thread2... 3 Exécution du thread1.... 6 Exécution du thread2... 4 Exécution du thread1.... 5 Exécution du thread2... 5 Exécution du thread1.... 4 Exécution du thread1.... 3 Exécution du thread1.... 2 Exécution du thread1.... 1 Exécution du thread1.... 0
On observe que thread1 et thread2 s'exécutent en alternance, car les deux intègrent une opération d'attente (mise en veille). Une fois que la fonction count_up termine son décompte jusqu'à 5, thread2 n'est plus actif. On obtient donc uniquement la sortie correspondant à thread1.
En résumé
Dans ce tutoriel, vous avez appris à exploiter le module de threading intégré de Python pour mettre en œuvre le multithreading. Voici un récapitulatif des points principaux à retenir :
- Le constructeur Thread sert à créer un objet thread. L'utilisation de threading.Thread(target=<fonction>,args=(<tuple d'arguments>)) permet de créer un thread qui exécute la fonction avec les arguments spécifiés.
- Le programme Python s'exécute sur un thread principal, de sorte que les objets thread que vous créez sont des threads additionnels. Il est possible d'appeler la fonction active_count() qui renvoie le nombre de threads actifs à un instant donné.
- Vous pouvez démarrer un thread en utilisant la méthode start() sur l'objet thread, et attendre la fin de son exécution en utilisant la méthode join().
Vous pouvez explorer d'autres exemples en modifiant les temps d'attente, en utilisant des opérations d'E/S différentes, etc. N'hésitez pas à mettre en œuvre le multithreading dans vos prochains projets Python. Bon codage !🎉