CodeGym /Cours /Python SELF FR /Module threading

Module threading

Python SELF FR
Niveau 25 , Leçon 1
Disponible

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(): Renvoie True 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 classe Thread.

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.

Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION