2.1 Module threading
La multithreading en Python est une méthode d'exécution de
plusieurs threads (threads)
simultanément, ce qui permet
d'utiliser plus efficacement les ressources du processeur, notamment pour les opérations
d'entrée/sortie ou d'autres tâches pouvant être effectuées en parallèle.
Concepts de base de la multithreading en Python :
Un Thread est la plus petite unité d'exécution pouvant fonctionner en parallèle avec d'autres threads dans un même processus. Tous les threads dans un processus partagent la même mémoire, ce qui permet l'échange de données entre les threads.
Un Processus est une instance d'un programme s'exécutant dans
un système d'exploitation, avec son propre espace d'adressage et ses ressources. Contrairement aux threads, les processus sont isolés les uns des autres et échangent
des données via une interaction interprocessus (IPC)
.
Le GIL
(Global Interpreter Lock) est un mécanisme dans l'interpréteur Python qui
empêche l'exécution simultanée de plusieurs threads Python. Le GIL
assure la sécurité de l'exécution du code Python, mais limite
les performances des programmes multithread sur les processeurs multicœurs.
Important ! Note que, en raison du Global Interpreter Lock (GIL), la multithreading en Python peut ne pas fournir une augmentation significative des performances pour les tâches intensives en calcul, car le GIL empêche l'exécution simultanée de plusieurs threads Python sur des processeurs multicœurs.
Module threading
Le module threading
en Python fournit une interface de haut niveau
pour travailler avec les threads. Il permet de créer et de gérer
des threads, de les synchroniser et d'organiser leur interaction.
Regardons de plus près les composants clés et les fonctions de ce module.
Composants principaux du module threading
Entités pour travailler avec les threads :
-
Thread
— classe principale pour créer et gérer des threads. -
Timer
— temporisateur pour exécuter une fonction après une période donnée. -
ThreadLocal
— permet de créer des données locales pour le thread.
Mécanisme de synchronisation des threads :
-
Lock
— primitive de synchronisation pour empêcher l'accès simultané aux ressources partagées. -
Condition
— variable conditionnelle pour une synchronisation des threads plus complexe. Event
— primitive pour la notification entre threads.-
Semaphore
— primitive pour limiter le nombre de threads pouvant exécuter une section donnée simultanément. -
Barrier
— synchronise un nombre donné de threads, les bloquant jusqu'à ce qu'ils atteignent tous la barrière.
Ci-dessous, je vais parler de 3 classes pour travailler avec les threads, mais le mécanisme de synchronisation des threads ne vous sera pas nécessaire pour le moment.
2.2 Classe Thread
La classe Thread
est la classe principale pour créer et gérer des threads. Elle
possède 4 méthodes principales :
start()
: Lance l'exécution du thread.-
join()
: Le thread actuel est suspendu et attend la fin du thread lancé. is_alive()
: RenvoieTrue
si le thread est en cours d'exécution.-
run()
: Méthode contenant le code à exécuter dans le thread. Elle est surchargée lors de l'héritage de la classeThread
.
C'est beaucoup plus simple qu'il n'y paraît - voici un exemple d'utilisation de la classe Thread
.
Lancer un thread simple
import threading
def worker():
print("Le thread worker est en cours d'exécution")
# Création d'un nouveau thread
t = threading.Thread(target=worker) #nous avons créé un nouvel objet thread
t.start() #Nous avons lancé le thread
t.join() # Nous attendons la fin du thread
print("Le thread principal est terminé")
Après l'appel de la méthode start, la fonction worker commencera son exécution. Ou, plus exactement, son thread sera ajouté à la liste des threads actifs.
Utilisation des arguments
import threading
def worker(number, text):
print(f"Worker {number}: {text}")
# Création d'un nouveau thread avec des arguments
t = threading.Thread(target=worker, args=(1, "Hello"))
t.start()
t.join()
Pour passer des paramètres dans un nouveau thread, il suffit de les indiquer sous forme de tuple
et de les assigner au paramètre args
. Lors de l'appel de la fonction spécifiée dans
le target
, les paramètres seront passés automatiquement.
Surcharge de la méthode run
import threading
class MyThread(threading.Thread):
def run(self):
print("Le thread personnalisé est en cours d'exécution")
# Création et lancement du thread
t = MyThread()
t.start()
t.join()
Il existe deux façons d'indiquer la fonction avec laquelle commencer l'exécution du nouveau
thread — vous pouvez la passer via le paramètre target
lors de la création de l'objet Thread
,
ou hériter de la classe Thread
et surcharger la fonction run
. Les deux
méthodes sont légales et utilisées constamment.
2.3 Classe Timer
La classe Timer
dans le module threading
est conçue pour exécuter une fonction après
un intervalle de temps donné. Cette classe est utile pour exécuter des
tâches différées dans un environnement multithread.
Le timer est créé et initialisé avec la fonction à appeler et le délai en secondes.
-
La méthode
start()
lance le timer qui compte l'intervalle de temps donné, puis appelle la fonction spécifiée. -
La méthode
cancel()
permet d'arrêter le timer s'il n'a pas encore fonctionné. Cela est utile pour empêcher l'exécution de la fonction si le timer n'est plus nécessaire.
Exemples d'utilisation :
Exécution d'une fonction avec délai
Dans cet exemple, la fonction hello
sera appelée 5 secondes après le lancement
du timer.
import threading
def hello():
print("Bonjour, le monde !")
# Création d'un timer qui appellera la fonction hello après 5 secondes
t = threading.Timer(5.0, hello)
t.start() # Lancement du timer
Arrêter le timer avant l'exécution
Ici, le timer sera arrêté avant que la fonction hello
n'ait pu s'exécuter
et, par conséquent, rien ne sera imprimé.
import threading
def hello():
print("Bonjour, le monde !")
# Création du timer
t = threading.Timer(5.0, hello)
t.start() # Lancement du timer
# Arrêter le timer avant l'exécution
t.cancel()
Timer avec arguments
Dans cet exemple, le timer appellera la fonction greet
après 3 secondes et lui passera
l'argument "Alice"
.
import threading
def greet(name):
print(f"Bonjour, {name}!")
# Création d'un timer avec des arguments
t = threading.Timer(3.0, greet, args=["Alice"])
t.start()
La classe Timer
est pratique pour planifier l'exécution de tâches après
un certain temps. Cependant, les timers ne garantissent pas un
temps d'exécution absolument précis, car cela dépend de la charge du système et de
la gestion des threads.
2.4 Classe ThreadLocal
La classe ThreadLocal
est conçue pour créer des threads qui ont
leurs propres données locales. Cela est utile dans les applications multithread
où chaque thread doit avoir sa propre version des données pour
éviter les conflits et les problèmes de synchronisation.
Chaque thread utilisant ThreadLocal
aura ses propres copies
indépendantes des données. Les données enregistrées dans un objet ThreadLocal
sont uniques pour chaque thread et ne sont pas partagées avec d'autres threads. Cela est pratique
pour stocker des données utilisées uniquement dans le contexte d'un seul thread,
comme l'utilisateur actuel dans une application web ou la connexion actuelle à
une base de données.
Exemples d'utilisation :
Utilisation principale
Dans cet exemple, chaque thread attribue son nom à la variable locale value
et l'affiche. La valeur value
est unique pour chaque thread.
import threading
# Création d'un objet ThreadLocal
local_data = threading.local()
def process_data():
# Attribution de la valeur à la variable locale du thread
local_data.value = threading.current_thread().name
# Accès à la variable locale du thread
print(f'Valeur dans {threading.current_thread().name}: {local_data.value}')
threads = []
for i in range(5):
t = threading.Thread(target=process_data)
threads.append(t)
t.start()
for t in threads:
t.join()
Stocker les données utilisateur dans une application web
Dans cet exemple, chaque thread traite la demande pour son utilisateur.
La valeur user_data.user
est unique pour chaque thread.
import threading
# Création d'un objet ThreadLocal
user_data = threading.local()
def process_request(user):
# Attribution de la valeur à la variable locale du thread
user_data.user = user
handle_request()
def handle_request():
# Accès à la variable locale du thread
print(f'Traitement de la demande pour l'utilisateur : {user_data.user}')
threads = []
users = ['Alice', 'Bob', 'Charlie']
for user in users:
t = threading.Thread(target=process_request, args=(user,))
threads.append(t)
t.start()
for t in threads:
t.join()
Ce sont les 3 classes les plus utiles de la bibliothèque threading
. Vous allez probablement
les utiliser dans votre travail, mais les autres classes — probablement pas.
Maintenant, tout le monde passe aux fonctions asynchrones et à
la bibliothèque asyncio
. C'est de cela que nous allons parler
dans l'avenir proche.
GO TO FULL VERSION