Le thread en Python – le module threading

Le thread est un concept utilisé par le système d’exploitation. Le système d’exploitation réalise ses processus en parallèle ou en multitâche en divisant le processus entre les threads. Un thread est la plus petite unité d’exécution et il garantit un flux d’exécution séparé.

Le thread en Python

En fait, les threads sont des sous-parties d’un processus volumineux qui peuvent s’exécuter simultanément en parallèle.

Les ordinateurs modernes peuvent exécuter plusieurs threads simultanément, ce qui nous donne la possibilité d’effectuer plusieurs tâches. Ce processus d’exécution simultanée de plusieurs threads qui permet d’effectuer des tâches en parallèle est appelé le multithreading.

Par exemple, une application monothread, ne peut traiter qu’une seule requête. Mais, parfois vous aurez besoin de traiter plusieurs requêtes en même temps. D’où la nécessite d’utiliser un multithread pour lancer plusieurs threads qui permettent un traitement parallèle.

Il existe différents types de threads:

  • Le thread du noyau
  • Le thread de l’utilisateur
  • La combinaison des deux threads.

Quels sont les avantages du multithreading en Python ?

  • Les threads du même processus partagent l’état et la mémoire du processus parent.
  • Ils ne nécessitent pas beaucoup de mémoire.
  • Une utilisation efficace des ressources.
  • Un gain de temps.
  • Les threads communiquent entre eux plus facilement puisque c’est le même processus.
  • Les serveurs multithreads et les interfaces graphiques interactives utilisent le multithreading.

Les inconvénients du multithreading

  • Les programmes multithreades ne s’exécutent pas rapidement si les systèmes informatiques ne sont pas dotés de plusieurs processeurs.
  • Le partage des ressources conduit à une plus grande utilisation de la mémoire et du processeur.
  • Le multithreading augmente la complexité du programme, ce qui rend également le débogage difficile.
  • Cela soulève la possibilité de blocages potentiels.

Le concept du threading

Quand on pense multitâches, on pense à une exécution parallèle. Le multithreading n’est pas une exécution strictement parallèle. Les threads peuvent être considérés comme des entités distinctes du flux d’exécution de différentes parties de votre programme s’exécutant indépendamment. Donc, essentiellement, les threads ne s’exécutent pas en parallèle, mais Python passe d’un thread à un autre si rapidement qu’il semble qu’ils sont parallèles.

Les processus, quant à eux, sont strictement parallèles et s’exécutent sur différents cœurs pour accélérer l’exécution. Les threads peuvent également être exécutés sur différents processeurs, mais ils ne fonctionneront toujours pas en parallèle techniquement.

Mais, si les threads ne fonctionnent pas en parallèle, comment peuvent-ils accélérer les choses ? La réponse est qu’ils n’accélèrent pas toujours le traitement. Le multithreading est spécifiquement utilisé dans les tâches où les threads accélèrent le traitement.

Toutes les informations d’un thread sont contenues dans le Thread Control Block (TCB). TCB se compose des parties principales suivantes :

1- Un TID unique – Identificateur de thread
2- Pointeur de pile qui pointe vers la pile de threads dans le processus
3- Un compteur de programme qui stocke l’adresse de l’instruction en cours d’exécution par le thread
4- État du thread (en cours d’exécution, prêt, en attente, démarré ou terminé)

Cela dit, les processus peuvent contenir plusieurs threads qui partagent le code, les données et tous les fichiers. Et tous les threads ont leur propre registre et pile séparés auxquels ils ont accès.

Maintenant, vous vous demandez peut-être si les threads utilisent les données et le code communs, comment peuvent-ils tous les utiliser sans gêner les autres threads. C’est la plus grande limitation du multithreading dont nous parlerons plus loin dans ce tutoriel.

Changement de contexte

On a vu que les threads ne fonctionnent pas en parallèle, ainsi, lorsqu’un thread T1 commence l’exécution, tous les autres threads restent en attente. Ce n’est qu’après que T1 ait terminé son exécution que tout autre thread en file d’attente peut commencer à s’exécuter. Python passe d’un thread à un autre si rapidement que cela ressemble à une exécution parallèle. Cette commutation est ce que nous appelons le «changement de contexte».

La programmation multithread

Considérez ci-dessous le code qui utilise des threads pour calculer le cube et le carré.

import threading

def cube(n):
    print(f"Le cube: {n * n * n}")

def carre(n):
    print(f"Le carré: {n * n}")

# création de thread
t1 = threading.Thread(target=carre, args=(3,))
t2 = threading.Thread(target=cube, args=(3,))

# démarrer le thread t1
t1.start()
# démarrer le thread t2
t2.start()

# attendre que t1 soit exécuté
t1.join()
# attendre que t2 soit exécuté
t2.join()

# les deux thread sont exécutés
print("C'est fini!")

L’exécution du code.

Le carré: 9
Le cube: 27
C'est fini!

Essayons maintenant de comprendre le code.

Tout d’abord, nous importons le module Threading qui est responsable de toutes les tâches. Nous créons 2 threads en créant des sous-classes de la classe Thread. Nous devons passer la cible, qui est la fonction qui doit être exécutée dans ce thread, et les arguments qui doivent être passés dans ces fonctions.

Maintenant, une fois que les threads sont déclarés, nous devons les démarrer. Cela se fait en appelant la méthode start() sur les threads. Une fois démarré, le programme principal doit attendre que les threads aient terminé leur traitement. Nous utilisons la méthode join() pour laisser le programme principal se mettre en pause et attendre que les threads t1 et t2 terminent leur exécution.

Synchronisation des threads

Comme nous l’avons vu ci-dessus, les threads ne s’exécutent pas en parallèle, mais Python passe de l’un à l’autre. Cependant, nous avons besoin de synchroniser les threads pour éviter tout comportement étrange.

La situation de concurrence

Les threads d’un même processus utilisent des données et des fichiers communs qui peuvent conduire à une concurrence entre plusieurs threads. Par conséquent, si une donnée est accédée par plusieurs threads, elle sera modifiée par tous les threads et les résultats que nous obtiendrons ne seront pas comme prévu.

Le but de la synchronisation des threads est de s’assurer que cette situation ne survient jamais et que les threads accèdent au données un par un de manière synchronisée.

Les verrous

Pour résoudre et éviter la concurence entre les threads, le module de thread propose une classe Lock qui utilise des sémaphores pour aider les threads à se synchroniser. Les sémaphores ne sont que des indicateurs binaires. Chaque fois qu’un thread rencontre un segment de code avec verrou, il doit vérifier si le verrou est défini sur la valeur 1 indiquant un état « occupé ». Si tel est le cas, il devra attendre que la valeur devienne 0 pour pouvoir l’utiliser, indiquant un état « libre ».

La classe Lock a deux méthodes principales:

acquire([blocking]): La méthode acquire() prend le paramètre de blocage comme vrai ou faux. Si un verrou pour un thread T1 a été initié avec un blocage True, il attendra ou restera bloqué jusqu’à ce que le code sera verrouillé par un autre thread T2. Une fois que l’autre thread T2 libère le verrou, le thread T1 acquiert le verrou et renvoie True.

De plus, si le verrou pour le thread T1 a été initié avec un blocage False, le thread T1 n’attendra pas ou ne restera pas bloqué si le code est déjà verrouillé par le thread T2. S’il le trouve verrouillé, il retournera immédiatement False et sortira. Cependant, si le code n’a pas été verrouillé par un autre thread, il acquiert le verrou et renvoie True.

release() : Lorsque cette méthode est appelée sur le verrou, elle le déverrouille et renvoie True. En outre, elle vérifiera si des threads attendent que le verrouillage soit libéré. S’il y en a, cela permettra à l’un d’entre eux d’accéder au verrou.

Cependant, si le verrou est déjà déverrouillé, une ThreadError sera déclenchée.

Les blocages

Un autre problème qui se pose lorsque nous traitons plusieurs verrous est – les blocages. Les blocages se produisent lorsque les verrous ne sont pas libérés par les threads pour diverses raisons.

Afin de surmonter ce problème, utilisez l’instruction with - as , également appelée les gestionnaires de contexte. Avec cette instruction, le verrou sera automatiquement libéré une fois le traitement terminé ou échoué pour une raison quelconque.

Conclusion

Comme nous l’avons vu, le multithreading n’est pas utile dans toutes les applications car il ne fait pas vraiment fonctionner les choses en parallèle. Mais l’utilisation principale du multithreading est avec les opérations d’entrées et sorties où le processeur reste inactif en attendant que les données soient chargées. Le multithreading joue ici un rôle crucial car ce temps d’inactivité du processeur est utilisé dans d’autres tâches, ce qui le rend idéal pour l’optimisation.

Le thread en Python – le module threading

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée.

Retour en haut