[Guide complet des threads Python] Des bases au multithreading sécurisé

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

Un thread Python est un mécanisme qui permet à plusieurs tâches de s’exécuter simultanément au sein d’un programme. En utilisant des threads, différentes parties du programme peuvent s’exécuter de manière concurrente sans s’attendre les unes les autres, améliorant ainsi l’efficacité. En Python, les threads peuvent être créés et gérés à l’aide du module threading.

Concept de base des threads

Un thread est une unité d’exécution légère qui s’exécute au sein d’un processus. Plusieurs threads peuvent s’exécuter indépendamment au sein d’un seul processus, permettant une exécution concurrente. Les threads sont particulièrement utiles pour les opérations d’E/S (telles que la lecture/écriture de fichiers et la communication réseau) et pour améliorer la réactivité des interfaces utilisateur.

Cas d’utilisation des threads en Python

Par exemple, lors de la création d’un outil de scraping web, l’accès à plusieurs pages web en parallèle peut réduire le temps de traitement global. De même, dans les applications de traitement de données en temps réel, les threads permettent des mises à jour en arrière-plan sans interrompre le traitement principal.

Ad

2. Comprendre le Global Interpreter Lock (GIL) en Python

Le Global Interpreter Lock (GIL) est un concept crucial dans le threading Python. Il s’agit d’un mécanisme qui restreint l’interpréteur Python à exécuter plus d’un thread à la fois.

Impact du GIL

Le GIL empêche plusieurs threads de s’exécuter simultanément, assurant la cohérence dans la gestion de la mémoire au sein d’un processus. Cependant, cette restriction limite les avantages du multithreading pour les tâches liées au CPU (tâches qui nécessitent un traitement CPU significatif). Par exemple, même si plusieurs threads effectuent des calculs complexes, seul un thread s’exécute à la fois en raison du GIL, ce qui entraîne une amélioration de performance limitée.

Façons de contourner le GIL

Pour contourner les limitations du GIL, vous pouvez utiliser le module multiprocessing pour paralléliser les tâches. Puisque chaque processus dans multiprocessing a son propre interpréteur Python indépendant, il n’est pas affecté par le GIL, permettant une exécution parallèle réelle.

Ad

3. Utilisation de base du module threading en Python

Le module threading est une bibliothèque standard en Python qui permet la création et la gestion des threads. Ici, nous couvrons son utilisation de base.

Création et exécution d’un thread

Pour créer un thread, utilisez la classe threading.Thread. Par exemple, vous pouvez créer et exécuter un thread comme suit :

import threading
import time

def my_function():
    time.sleep(2)
    print("Thread executed")

# Creating a thread
thread = threading.Thread(target=my_function)

# Starting the thread
thread.start()

# Waiting for the thread to finish
thread.join()
print("Main thread completed")

Dans cet exemple, un nouveau thread est créé et exécute my_function de manière asynchrone.

Synchronisation des threads

Pour attendre qu’un thread se termine, utilisez la méthode join(). Cette méthode met en pause le thread principal jusqu’à ce que le thread spécifié se termine, assurant la synchronisation entre les threads.

Ad

4. Création d’un thread par sous-classement de la classe Thread

Vous pouvez créer un thread personnalisé en sous-classant la classe threading.Thread.

Ad

Sous-classement de Thread

L’exemple suivant démontre comment sous-classer la classe Thread et surcharger la méthode run() pour définir un thread personnalisé.

import threading
import time

class MyThread(threading.Thread):
    def run(self):
        time.sleep(2)
        print("Custom thread executed")

# Creating and running a custom thread
thread = MyThread()
thread.start()
thread.join()
print("Main thread completed")

Avantages du sous-classement

Le sous-classement permet d’encapsuler le comportement du thread, rendant le code plus réutilisable. Il permet également une gestion flexible des threads, comme l’assignation de données différentes à chaque thread.

Ad

5. Sécurité des threads et synchronisation

Lorsque plusieurs threads accèdent à la même ressource, une synchronisation est requise pour maintenir l’intégrité des données.

Condition de course

Une condition de course se produit lorsque plusieurs threads modifient la même ressource simultanément, ce qui entraîne des résultats imprévisibles. Par exemple, si plusieurs threads incrémentent une variable de compteur sans synchronisation appropriée, la valeur finale peut être incorrecte.

Synchronisation avec des verrous

Le module threading fournit un objet Lock pour la synchronisation des threads. L’utilisation d’un Lock garantit qu’un seul thread peut accéder à une ressource à la fois, empêchant les conditions de course.

import threading

counter = 0
lock = threading.Lock()

def increment_counter():
    global counter
    with lock:
        counter += 1

threads = []
for _ in range(100):
    thread = threading.Thread(target=increment_counter)
    threads.append(thread)
    thread.start()

for thread in threads:
    thread.join()

print("Final counter value:", counter)

Dans cet exemple, le bloc with lock garantit que le compteur est incrémenté en toute sécurité, empêchant l’incohérence des données.

Ad

6. Threads pour les tâches liées à l’E/S vs liées au CPU

Les threads sont particulièrement efficaces pour les tâches liées à l’E/S, telles que les opérations sur les fichiers et la communication réseau.

Avantages des threads pour les tâches liées à l’E/S

Les tâches liées à l’E/S passent une quantité de temps significative dans un état d’attente. L’utilisation de threads pour gérer plusieurs opérations E/S de manière concurrente améliore l’efficacité globale. Par exemple, un programme peut lire/écrire des fichiers tout en gérant simultanément la communication réseau, réduisant le temps d’inactivité.

Tâches liées au CPU et multiprocessing

Pour les tâches liées au CPU (telles que les calculs numériques et le traitement des données), il est recommandé d’utiliser le module multiprocessing au lieu de threading. Puisque multiprocessing n’est pas affecté par le verrou d’interpréteur global (GIL), il permet une utilisation efficace de plusieurs cœurs CPU.

Ad

7. Gestion des threads

Voici quelques techniques pour gérer efficacement les threads Python.

Nommer et identifier les threads

L’attribution de noms aux threads facilite le débogage et la journalisation. Vous pouvez spécifier le nom du thread en utilisant l’argument name de threading.Thread.

import threading

def task():
    print(f"Thread {threading.current_thread().name} is running")

thread1 = threading.Thread(target=task, name="Thread1")
thread2 = threading.Thread(target=task, name="Thread2")

thread1.start()
thread2.start()

Vérification du statut des threads

Pour vérifier si un thread est actuellement en cours d’exécution, utilisez la méthode is_alive(). Cette méthode retourne True si le thread est encore en cours d’exécution et False s’il a terminé.

import threading
import time

def task():
    time.sleep(1)
    print("Task completed")

thread = threading.Thread(target=task)
thread.start()

if thread.is_alive():
    print("Thread is still running")
else:
    print("Thread has finished")
Ad

8. Comparaison : Threads vs multiprocessing

Comprendre les différences entre les threads et les processus aide à déterminer le cas d’utilisation approprié pour chacun.

Avantages et inconvénients des threads

Les threads sont légers et partagent la mémoire au sein du même processus, ce qui les rend efficaces pour les tâches liées à l’E/S. Cependant, en raison du verrou d’interpréteur global (GIL), leurs performances sont limitées pour les tâches liées au CPU.

Avantages de multiprocessing

Le module multiprocessing permet une exécution parallèle réelle en assignant des interpréteurs Python indépendants à chaque processus. Cela est bénéfique pour les tâches intensives en CPU mais nécessite un surcoût supplémentaire pour la communication inter-processus.

Ad

9. Meilleures pratiques pour le module threading en Python

Suivre les meilleures pratiques en programmation multithreadée garantit un fonctionnement stable et un débogage plus facile.

Arrêt sécurisé des threads

Évitez d’arrêter de force les threads. Au lieu de cela, utilisez des drapeaux ou des variables de condition pour contrôler leur sortie. De plus, assurez-vous que les ressources sont correctement libérées lors de l’arrêt d’un thread.

Prévention des blocages

Pour prévenir les blocages lors de l’utilisation de verrous pour la synchronisation des threads, suivez ces directives :

  • Maintenir un ordre d’acquisition des verrous cohérent.
  • Minimiser la portée des verrous.
  • Utiliser l’instruction with pour garantir la libération automatique du verrou.

10. Conclusion

Le module threading de Python est un outil puissant pour l’exécution concurrente. Ce guide a couvert l’utilisation de base, l’impact du GIL, les différences entre le threading et le multiprocessing, ainsi que les meilleures pratiques pour une gestion sûre des threads.

Bien que les threads soient idéaux pour les tâches I/O‑bound, il est crucial de comprendre le GIL et de choisir l’approche appropriée à votre cas d’utilisation. En suivant les meilleures pratiques, vous pouvez améliorer les performances et la fiabilité de vos programmes Python.

Ad
RUNTEQ(ランテック)|超実戦型エンジニア育成スクール