Multithreading en Python [avec exemples de codage]

Publié: 2020-11-30

Améliorer et rendre le code plus rapide est la prochaine étape après l'acquisition des connaissances de base de Python. Le multithreading est l'un de ces moyens d'obtenir cette optimisation à l'aide de "Threads". Quels sont ces fils? Et en quoi sont-ils différents des processus ? Découvrons-le.

À la fin de ce tutoriel, vous aurez les connaissances suivantes :

  • Que sont les threads et les processus ?
  • Comment le multithreading est-il réalisé ?
  • Quelles sont ses limites ?
  • Quand utiliser le multithreading ?

Apprenez des cours de science des données en ligne dans les meilleures universités du monde. Gagnez des programmes Executive PG, des programmes de certificat avancés ou des programmes de maîtrise pour accélérer votre carrière.

Table des matières

Threads en Python

Quand on pense multitâche, on pense 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 à l'autre si rapidement qu'il semble qu'ils soient parallèles.

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

Et pensez-vous que si les threads ne s'exécutent pas en parallèle, comment accélèrent-ils 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 - Identifiant de fil
  2. Stack Pointer qui pointe vers la pile du thread 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 tard dans ce tutoriel.

Changement de contexte

Maintenant, comme décrit ci-dessus, les threads ne s'exécutent pas en parallèle, mais en conséquence. Ainsi, lorsqu'un thread T1 démarre son exécution, tous les autres threads restent en mode d'attente. Ce n'est qu'après que T1 a 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 la « commutation de contexte ».

Programmation multithread

Considérez ci-dessous le code qui utilise des threads pour effectuer un cube et une opération carrée.

importer le thread

def cuber (n) :
print( "Cube : {}" .format(n * n * n))

def au carré (n) :
print( "Carré : {}" .format(n * n))

si __nom__ == "__main__" :
# créer le fil
t1 = threading.Thread(target=squarer, args=( 5 ,))
t2 = threading.Thread(target=cuber, args=( 5 ,))

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

# attendre que t1 soit terminé
t1.join()
# attendre que t2 soit terminé
t2.join()

# les deux threads sont terminés
print( "Terminé!" )

#Sortir:
Carré : 25
Cube : 125
Terminé!

Essayons maintenant de comprendre le code.

Tout d'abord, nous importons le module Threading qui est responsable de toutes les tâches. Dans le main, 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 les threads 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 terminent leur traitement. Nous utilisons la méthode d' attente pour laisser le programme principal faire une pause et attendre que les threads T1 et T2 terminent leur exécution.

Doit lire : Défis Python pour les débutants

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. Il existe donc un besoin très critique de synchronisation correcte entre les threads pour éviter tout comportement étrange.

Condition de course

Les threads qui sont sous le même processus utilisent des données et des fichiers communs qui peuvent conduire à une "course" pour les données entre plusieurs threads. Par conséquent, si une donnée est accessible par plusieurs threads, elle sera modifiée par les deux threads et les résultats que nous obtiendrons ne seront pas ceux attendus. C'est ce qu'on appelle une condition de course.

Ainsi, si vous avez deux threads qui ont accès aux mêmes données, ils peuvent tous les deux y accéder et les modifier lorsque ce thread particulier est en cours d'exécution. Ainsi, lorsque T1 commence à s'exécuter et modifie certaines données, T2 est en mode veille/attente. Ensuite, T1 arrête l'exécution et passe en mode veille en cédant le contrôle à T2, qui a également accès aux mêmes données. Ainsi, T2 va maintenant modifier et écraser les mêmes données, ce qui entraînera des problèmes lorsque T1 recommencera.

L'objectif de la synchronisation des threads est de s'assurer que cette condition de concurrence ne survient jamais et que la section critique du code est accessible par les threads un à la fois de manière synchronisée.

Serrures

Pour résoudre et prévenir la Race Condition et ses conséquences, le module thread propose une classe Lock qui utilise des sémaphores pour aider les threads à se synchroniser. Les sémaphores ne sont que des drapeaux binaires. Considérez-les comme le signe "Engagé" sur les cabines téléphoniques qui ont la valeur "Engagé" (équivalent à 1) ou "Non engagé" (équivalent à 0). Ainsi, chaque fois qu'un thread rencontre un segment de code avec verrou, il doit vérifier si le verrou est déjà dans 1 état. Si c'est le cas, il devra attendre qu'il devienne 0 pour pouvoir l'utiliser.

La classe Lock a deux méthodes principales :

  1. acquérir([blocage]) : la méthode d' acquisition prend le paramètre de blocage en tant que True ou False . Si un verrou pour un thread T1 a été initié avec le blocage sur True, il attendra ou restera bloqué jusqu'à ce que la section critique du code soit verrouillée 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 .

D'un autre côté, si le verrou du thread T1 a été initié avec le blocage de paramètre comme False , le thread T1 n'attendra pas ou ne restera pas bloqué si la section critique est déjà verrouillée par le thread T2. S'il le voit comme verrouillé, il renverra immédiatement False et quittera. Cependant, si le code n'a pas été verrouillé par un autre thread, il acquerra le verrou et renverra True .

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

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

Blocages

Un autre problème qui se pose lorsque nous traitons plusieurs verrous est le blocage. Les interblocages se produisent lorsque les verrous ne sont pas libérés par les threads pour diverses raisons. Prenons un exemple simple où nous procédons comme suit :

importer le thread

l = threading.Lock()
# Avant la 1ère acquisition
l.acquérir()
# Avant la 2ème acquisition
l.acquérir()
# Maintenant acquis le verrou deux fois

Dans le code ci-dessus, nous appelons la méthode d'acquisition deux fois mais ne la relâchons pas après son acquisition pour la première fois. Par conséquent, lorsque Python verra la deuxième instruction d'acquisition, il passera indéfiniment en mode d'attente car nous n'avons jamais libéré le verrou précédent.

Ces conditions de blocage peuvent se glisser dans votre code sans que vous vous en rendiez compte. Même si vous incluez un appel de libération, votre code peut échouer à mi-chemin et la libération ne sera jamais appelée et le verrou restera verrouillé. Une façon de surmonter cela consiste à utiliser l' instruction with as , également appelée les gestionnaires de contexte. En utilisant l' instruction with as , le verrou sera automatiquement libéré une fois le traitement terminé ou échoué pour une raison quelconque.

Lire : Idées et sujets de projet Python

Avant que tu partes

Comme nous l'avons vu précédemment, le multithreading n'est pas utile dans toutes les applications car il ne fait pas vraiment fonctionner les choses en parallèle. Mais l'application principale du multithreading concerne les tâches d'E / S 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.

Si vous êtes curieux d'en savoir plus sur la science des données, consultez le programme Executive PG en science des données de IIIT-B & upGrad qui est créé pour les professionnels en activité et propose plus de 10 études de cas et projets, des ateliers pratiques, un mentorat avec des experts de l'industrie, 1 -on-1 avec des mentors de l'industrie, plus de 400 heures d'apprentissage et d'aide à l'emploi avec les meilleures entreprises.

Qu'est-ce qu'un thread en Python ?

Les threads sont des entités au sein d'un processus dont l'exécution peut être planifiée en Python. En termes simples, un thread est un processus de calcul effectué par un ordinateur. Il s'agit d'un ensemble d'instructions de ce type dans un programme que les développeurs peuvent exécuter indépendamment d'autres scripts. Les threads vous permettent d'augmenter la vitesse de l'application en utilisant le parallélisme. Il s'agit d'un processus léger qui permettra aux tâches de fonctionner en parallèle. Les threads fonctionnent indépendamment et maximisent l'utilisation du processeur, améliorant ainsi les performances du processeur.

A quoi sert le multi-thread en Python ?

Le multithreading est une technique de threading dans la programmation Python qui permet à de nombreux threads de fonctionner simultanément en basculant rapidement entre les threads avec l'aide d'un processeur (appelé changement de contexte). Lorsque nous pouvons diviser notre tâche en plusieurs sections distinctes, nous utilisons le multithreading. Par exemple, supposons que vous deviez effectuer une requête de base de données complexe pour obtenir des données et diviser cette requête en plusieurs requêtes individuelles. Dans ce cas, il sera préférable d'allouer un thread à chaque requête et de les exécuter toutes en parallèle.

Qu'est-ce que la synchronisation des threads ?

La synchronisation des threads est décrite comme une méthode qui garantit que deux ou plusieurs processus ou threads simultanés n'exécutent pas simultanément une partie cruciale d'un programme. Les méthodes de synchronisation sont utilisées pour contrôler l'accès des processus aux sections importantes. Lorsque nous démarrons deux threads ou plus dans un programme, il est possible que plusieurs threads tentent d'accéder à la même ressource, ce qui entraîne des résultats inattendus en raison de problèmes de concurrence. Par exemple, si plusieurs threads tentent d'écrire dans le même fichier, les données peuvent être corrompues car l'un des threads peut remplacer les données ou lorsqu'un thread s'ouvre et qu'un autre thread ferme le même fichier.