Utilisation facile des threads

PythonBeginner
Pratiquer maintenant

Introduction

Dans ce tutoriel, nous allons apprendre à utiliser le module threading de Python pour exécuter plusieurs threads d'exécution simultanément.

Le module threading de Python fournit un moyen simple de créer et de gérer des threads dans un programme Python. Un thread est un flux d'exécution séparé à l'intérieur d'un programme. En exécutant plusieurs threads simultanément, nous pouvons profiter des processeurs multi-cœurs et améliorer les performances de nos programmes.

Le module threading fournit deux classes pour créer et gérer des threads :

  1. Classe Thread : Cette classe représente un seul thread d'exécution.
  2. Classe Lock : Cette classe permet de synchroniser l'accès à des ressources partagées entre les threads.

Création de threads

Pour créer un nouveau thread en Python, nous devons créer une nouvelle instance de la classe Thread et lui passer une fonction à exécuter.

Créez un projet appelé create_thread.py dans le WebIDE et entrez le contenu suivant.

import threading

## Définissez une fonction à exécuter dans le thread
def my_function():
    print("Hello from thread")

## Créez un nouveau thread
thread = threading.Thread(target=my_function)

## Démarrez le thread
thread.start()

## Attendez que le thread se termine
thread.join()

## Affichez "Terminé" pour indiquer que le programme est terminé
print("Terminé")

Cet exemple définit une fonction my_function qui imprime un message. Nous créons ensuite une nouvelle instance de la classe Thread, lui passant my_function comme fonction cible. Enfin, nous démarrons le thread en utilisant la méthode start et attendons qu'il se termine en utilisant la méthode join.

Utilisez la commande suivante pour exécuter le script.

python create_thread.py

Synchronisation

Si plusieurs threads accèdent à la même ressource partagée (par exemple, une variable ou un fichier), nous devons synchroniser l'accès à cette ressource pour éviter les conditions de course. Le module threading de Python fournit une classe Lock à cette fin.

Voici un exemple d'utilisation d'un Lock pour créer un projet appelé sync.py dans le WebIDE et entrer le contenu suivant.

import threading

## Créez un verrou pour protéger l'accès à la ressource partagée
lock = threading.Lock()

## Ressource partagée qui sera modifiée par plusieurs threads
compteur = 0

## Définissez une fonction que chaque thread exécutera
def ma_fonction():
    global compteur

    ## Acquirez le verrou avant d'accéder à la ressource partagée
    lock.acquire()
    try:
        ## Accédez à la ressource partagée
        compteur += 1
    finally:
        ## Libérez le verrou après avoir modifié la ressource partagée
        lock.release()

## Créez plusieurs threads pour accéder à la ressource partagée
threads = []

## Créez et lancez 10 threads qui exécutent la même fonction
for i in range(10):
    thread = threading.Thread(target=ma_fonction)
    threads.append(thread)
    thread.start()

## Attendez que tous les threads aient terminé leur exécution
for thread in threads:
    thread.join()

## Affichez la valeur finale du compteur
print(compteur) ## Sortie : 10

Nous créons un objet Lock et une ressource partagée compteur dans cet exemple. La fonction ma_fonction accède à la ressource partagée en acquérant le verrou à l'aide de la méthode acquire et en libérant le verrou à l'aide de la méthode release. Nous créons plusieurs threads et les lançons, puis attendons qu'ils se terminent à l'aide de la méthode join. Enfin, nous affichons la valeur finale du compteur.

Utilisez la commande suivante pour exécuter le script.

python sync.py

Thread avec arguments

En Python, vous pouvez passer des arguments aux threads en utilisant soit le paramètre args lors de la création d'un nouveau thread, soit en créant une sous-classe de la classe Thread et en définissant votre constructeur qui accepte des arguments. Voici des exemples des deux approches :

1; Nous créons une sous-classe de la classe Thread et redéfinissons la méthode run pour définir le comportement du thread. Créez un projet appelé thread_subclass.py dans le WebIDE et entrez le contenu suivant.

import threading

## Définissez une classe Thread personnalisée qui étend la classe Thread
class MonThread(threading.Thread):
    ## Redéfinissez la méthode run() pour implémenter le comportement du thread
    def run(self):
        print("Bonjour depuis le thread")

## Créez une instance de la classe de thread personnalisée
thread = MonThread()

## Démarrez le thread
thread.start()

## Attendez que le thread ait terminé son exécution
thread.join()

Utilisez la commande suivante pour exécuter le script.

python thread_subclass.py

2; Nous créons un thread et passons des arguments à la fonction cible en utilisant le paramètre args. Créez un projet appelé thread_with_args.py dans le WebIDE et entrez le contenu suivant

import threading

## Définissez une fonction qui prend un paramètre
def ma_fonction(nom):
    print("Bonjour de", nom)

## Créez un nouveau thread avec la fonction cible et les arguments
thread = threading.Thread(target=ma_fonction, args=("Thread 1",))

## Démarrez le thread
thread.start()

## Attendez que le thread ait terminé son exécution
thread.join()

Utilisez la commande suivante pour exécuter le script.

python thread_with_args.py

Pool de threads

En Python, vous pouvez utiliser un thread pool pour exécuter des tâches simultanément à l'aide d'un ensemble prédéfini de threads. L'avantage d'utiliser un thread pool est qu'il évite la surcharge de la création et de la destruction de threads pour chaque tâche, ce qui peut améliorer les performances.

Le module concurrent.futures de Python fournit une classe ThreadPoolExecutor qui vous permet de créer un pool de threads et de soumettre des tâches. Voici un exemple :

Créez un projet appelé thread_pool_range.py dans le WebIDE et entrez le contenu suivant.

import concurrent.futures

## Définissez une fonction à exécuter dans plusieurs threads avec deux arguments
def ma_fonction(arg1, arg2):
    ## Définissez les tâches effectuées par le thread ici
    print(f"Bonjour depuis mon thread avec les arguments {arg1} et {arg2}")

## Créez un objet ThreadPoolExecutor avec un maximum de 5 threads de travail
with concurrent.futures.ThreadPoolExecutor(max_workers=5) as exécuteur:
    ## Soumettez chaque tâche (appel de fonction avec ses arguments) à l'exécuteur pour traitement dans un thread séparé
    ## La méthode submit() renvoie un objet Future représentant le résultat de la computation asynchrone
    for i in range(10):
        exécuteur.submit(ma_fonction, i, i+1)

Dans cet exemple, nous définissons une fonction ma_fonction qui prend deux arguments. Nous créons un ThreadPoolExecutor avec un maximum de 5 threads de travail. Ensuite, nous parcourons une plage de nombres et soumettons des tâches au thread pool à l'aide de la méthode exécuteur.submit(). Chaque tâche soumise est exécutée sur l'un des threads de travail disponibles.

Conseils : L'objet ThreadPoolExecutor est utilisé comme gestionnaire de contexte. Cela garantit que tous les threads sont correctement nettoyés lorsque le code à l'intérieur du bloc with est terminé.

Utilisez la commande suivante pour exécuter le script.

python thread_pool_range.py

La méthode submit() renvoie immédiatement un objet Future, représentant le résultat de la tâche soumise. Vous pouvez utiliser la méthode result() de l'objet Future pour récupérer la valeur de retour de la tâche. Si la tâche lève une exception, l'appel à result() levera cette exception.

Vous pouvez également utiliser la méthode map() de la classe ThreadPoolExecutor pour appliquer la même fonction à une collection d'éléments. Par exemple, Créez un projet appelé thread_pool_map.py dans le WebIDE et entrez le contenu suivant. :

import concurrent.futures

## Définissez une fonction à exécuter dans plusieurs threads
def ma_fonction(item):
    ## Définissez les tâches effectuées par le thread ici
    print(f"Bonjour depuis mon thread avec l'argument {item}")

## Créez une liste d'éléments à traiter par les threads
éléments = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

## Créez un objet ThreadPoolExecutor avec un maximum de 5 threads de travail
with concurrent.futures.ThreadPoolExecutor(max_workers=5) as exécuteur:
    ## Soumettez chaque élément à l'exécuteur pour traitement dans un thread séparé
    ## La méthode map() renvoie automatiquement les résultats dans l'ordre
    exécuteur.map(ma_fonction, éléments)

Dans cet exemple, nous définissons une fonction ma_fonction qui prend un argument. Nous créons une liste d'éléments et les soumettons au thread pool à l'aide de la méthode exécuteur.map(). Chaque élément de la liste est passé à ma_fonction en tant qu'argument, et chaque élément est exécuté sur l'un des threads de travail disponibles.

Utilisez la commande suivante pour exécuter le script.

python thread_pool_map.py

Les résultats obtenus à partir de thread_pool_range.py et thread_pool_map.py sont les mêmes.

Threads de démon

En Python, un thread démon est un type de thread qui s'exécute en arrière-plan et ne bloque pas la sortie du programme. Lorsque tous les threads non-démon ont été terminés, l'interpréteur Python quitte le programme, quelle que soit l'état d'exécution des threads démon.

Créez un projet appelé daemon_thread_with_args.py dans le WebIDE et entrez le contenu suivant.

import threading
import time

## Définissez une fonction qui s'exécute indéfiniment et affiche des messages à intervalles réguliers
def ma_fonction():
    while True:
        print("Bonjour depuis le thread")
        time.sleep(1)

## Créez un thread démon qui exécute la fonction cible
thread = threading.Thread(target=ma_fonction, daemon=True)

## Démarrez le thread
thread.start()

## Le programme principal continue d'exécuter et affiche un message
print("Programme principal")

## Attendez quelques secondes avant de quitter le programme
time.sleep(5)

## Le programme quitte et le thread démon est terminé automatiquement
print("Sortie du thread principal...")

Dans cet exemple, nous créons un thread qui exécute une boucle infinie et affiche un message toutes les secondes à l'aide de la fonction time.sleep(). Nous marquons le thread comme démon en utilisant le paramètre daemon pour qu'il quitte automatiquement lorsque le programme principal se termine. Le programme principal continue de s'exécuter et affiche un message. Nous attendons quelques secondes, puis le programme quitte, terminant le thread démon.

Ensuite, utilisez la commande suivante pour exécuter le script.

python daemon_thread_with_args.py

Bien sûr, nous pouvons également définir un thread comme démon en appelant la méthode setDaemon(True) sur l'instance du thread. Par exemple, Créez un projet appelé daemon_thread_with_func.py dans le WebIDE et entrez le contenu suivant :

import threading
import time

## Définissez une fonction qui s'exécute indéfiniment et affiche des messages à intervalles réguliers
def ma_fonction():
    while True:
        print("Bonjour depuis le thread")
        time.sleep(1)

## Créez un nouveau thread avec la fonction cible
thread = threading.Thread(target=ma_fonction)

## Définissez le drapeau démon sur True pour que le thread s'exécute en arrière-plan et se termine lorsque le programme principal quitte
thread.setDaemon(True)

## Démarrez le thread
thread.start()

## Le programme principal continue de s'exécuter et affiche un message
print("Programme principal")

## Attendez quelques secondes avant de quitter le programme
time.sleep(5)

## Le programme quitte et le thread démon est terminé automatiquement
print("Sortie du thread principal...")

Exécuter le script avec la commande suivante donnera le même résultat que l'exemple ci-dessus.

python daemon_thread_with_func.py

Objet d'événement

En Python, vous pouvez utiliser l'objet threading.Event pour permettre à des threads de attendre qu'un événement spécifique se produise avant de continuer. L'objet d'événement fournit un moyen pour un thread de signaler qu'un événement est survenu, et d'autres threads peuvent attendre ce signal.

Créez un projet appelé event_object.py dans le WebIDE et entrez le contenu suivant.

import threading

## Créez un objet d'événement
événement = threading.Event()

## Définissez une fonction qui attend que l'événement soit défini
def ma_fonction():
    print("En attente de l'événement")
    ## Attendez que l'événement soit défini
    événement.wait()
    print("Événement reçu")

## Créez un nouveau thread avec la fonction cible
thread = threading.Thread(target=ma_fonction)

## Démarrez le thread
thread.start()

## Signalez l'événement après quelques secondes
## L'appel à wait() dans la fonction cible retournera désormais et la poursuite de l'exécution se poursuivra
événement.set()

## Attendez que le thread ait terminé son exécution
thread.join()

Dans cet exemple, nous créons un objet Event à l'aide de la classe Event. Nous définissons une fonction qui attend que l'événement soit signalé à l'aide de la méthode wait puis affiche un message. Nous créons un nouveau thread et le lançons. Après quelques secondes, nous signalons l'événement à l'aide de la méthode set. Le thread reçoit l'événement et affiche un message. Enfin, nous attendons que le thread se termine à l'aide de la méthode join.

Ensuite, utilisez la commande suivante pour exécuter le script.

python event_object.py

Objet Timer

En Python, vous pouvez utiliser l'objet threading.Timer pour planifier une fonction à exécuter après un certain temps s'est écoulé. L'objet Timer crée un nouveau thread qui attend l'intervalle de temps spécifié avant d'exécuter la fonction.

Créez un projet appelé timer_object.py dans le WebIDE et entrez le contenu suivant.

import threading

## Définissez une fonction à exécuter par le Timer après 5 secondes
def ma_fonction():
    print("Bonjour depuis le timer")

## Créez un timer qui exécute la fonction cible après 5 secondes
timer = threading.Timer(5, ma_fonction)

## Démarrez le timer
timer.start()

## Attendez que le timer se termine
timer.join()

Dans cet exemple, nous créons un objet Timer à l'aide de la classe Timer et lui passons un délai de temps en secondes et une fonction à exécuter. Nous démarrons le timer à l'aide de la méthode start et attendons qu'il se termine à l'aide de la méthode join. Après 5 secondes, la fonction est exécutée et affiche un message.

Ensuite, utilisez la commande suivante pour exécuter le script.

python timer_object.py

Conseils : Le thread du timer s'exécute séparément, donc il peut ne pas être synchronisé avec le thread principal. Si votre fonction dépend d'un état ou de ressources partagés, vous devrez synchroniser l'accès à ces éléments de manière appropriée. N'oubliez pas également que le thread du timer ne stoppera pas le programme de sortir s'il est toujours en cours d'exécution lorsque tous les autres threads non-démon ont été terminés.

Objet Barrière

En Python, vous pouvez utiliser l'objet threading.Barrier pour synchroniser plusieurs threads à des points de synchronisation prédéfinis. L'objet Barrière fournit un moyen pour un ensemble de threads de s'attendre mutuellement à atteindre un certain point dans leur exécution avant de continuer.

Créez un projet appelé barrier_object.py dans le WebIDE et entrez le contenu suivant.

import threading

## Créez un objet Barrière pour 3 threads
barrière = threading.Barrier(3)

## Définissez une fonction qui attend à la barrière
def ma_fonction():
    print("Avant la barrière")
    ## Attendez que les trois threads atteignent la barrière
    barrière.wait()
    print("Après la barrière")

## Créez 3 threads à l'aide d'une boucle et démarréz-les
threads = []
for i in range(3):
    thread = threading.Thread(target=ma_fonction)
    threads.append(thread)
    thread.start()

## Attendez que tous les threads aient terminé leur exécution
for thread in threads:
    thread.join()

Dans cet exemple, nous créons un objet Barrière à l'aide de la classe Barrière et lui passons le nombre de threads à attendre. En utilisant la méthode wait, nous définissons une fonction qui attend la barrière et affiche un message. Nous créons trois threads et les démarrons. Chaque thread attend la barrière, donc tous les threads attendront jusqu'à ce que tous aient atteint la barrière. Enfin, nous attendons que tous les threads aient fini à l'aide de la méthode join.

Ensuite, utilisez la commande suivante pour exécuter le script.

python barrier_object.py

Résumé

C'est tout! Maintenant, vous savez comment utiliser le module threading de Python dans votre code. Cela peut nous aider à maîtriser en profondeur les principes et les techniques de base de la programmation concurrente afin que nous puissions développer plus efficacement des applications concurrentes.