Tutoriel Threading en Python
Tutoriel Threading en Python
Exemple
Supposons que nous exécutions un processus particulier dans lequel nous ouvrons MS Word
pour y saisir du contenu. Un fil sera assigné pour ouvrir MS Word et un autre fil sera nécessaire
pour y taper du contenu. Et maintenant, si nous voulons modifier l'existant, un autre thread sera
nécessaire pour effectuer la tâche d'édition et ainsi de suite.
Multiprocessing,d'autre part, est l'utilisation de deux ou plusieurs unités CPU dans un seul
système informatique. Notre objectif principal est d'exploiter pleinement le potentiel de notre
matériel. Pour y parvenir, nous devons utiliser le nombre total de cœurs de processeur
disponibles dans notre système informatique. Le multitraitement est la meilleure approche pour
ce faire.
Python est l'un des langages de programmation les plus populaires. Voici quelques raisons qui le
rendent adapté aux applications simultanées -
Sucre syntaxique
Le sucre syntaxique est une syntaxe dans un langage de programmation conçu pour rendre les
choses plus faciles à lire ou à exprimer. Cela rend le langage «plus doux» pour l'usage humain:
les choses peuvent être exprimées plus clairement, plus concis ou dans un style alternatif basé
sur les préférences. Python est livré avec des méthodes Magic, qui peuvent être définies pour
agir sur des objets. Ces méthodes Magic sont utilisées comme sucre syntaxique et liées à des
mots-clés plus faciles à comprendre.
Grande communauté
Le langage Python a connu un taux d'adoption massif parmi les scientifiques des données et les
mathématiciens, travaillant dans le domaine de l'IA, de l'apprentissage automatique, de
l'apprentissage en profondeur et de l'analyse quantitative.
Concurrence vs parallélisme
La simultanéité et le parallélisme sont utilisés en relation avec les programmes multithread, mais
il y a beaucoup de confusion sur la similitude et la différence entre eux. La grande question à cet
égard: le parallélisme de concurrence est-il ou non? Bien que les deux termes semblent assez
similaires, mais que la réponse à la question ci-dessus soit NON, la concurrence et le
parallélisme ne sont pas les mêmes. Maintenant, s'ils ne sont pas les mêmes, quelle est la
différence fondamentale entre eux?
En termes simples, la concurrence traite de la gestion de l'accès à l'état partagé à partir de
différents threads et de l'autre côté, le parallélisme traite de l'utilisation de plusieurs processeurs
ou de ses cœurs pour améliorer les performances du matériel.
Concurrence en détail
La concurrence se produit lorsque deux tâches se chevauchent lors de l'exécution. Il peut s'agir
d'une situation où une application progresse sur plus d'une tâche à la fois. Nous pouvons le
comprendre schématiquement; plusieurs tâches progressent en même temps, comme suit -
Niveaux de concurrence
Dans cette section, nous aborderons les trois niveaux importants de concurrence en termes de
programmation -
Propriété d'exactitude
La propriété d'exactitude signifie que le programme ou le système doit fournir la bonne réponse
souhaitée. Pour faire simple, nous pouvons dire que le système doit correctement mapper l'état
du programme de départ à l'état final.
Propriété de sécurité
La propriété de sécurité signifie que le programme ou le système doit rester dans
un “good” ou “safe” état et ne fait jamais rien “bad”.
Propriété vivante
Cette propriété signifie qu'un programme ou un système doit “make progress” et il atteindrait un
état souhaitable.
Acteurs de systèmes concurrents
Il s'agit d'une propriété commune du système concurrent dans lequel il peut y avoir plusieurs
processus et threads, qui s'exécutent en même temps pour progresser sur leurs propres tâches.
Ces processus et threads sont appelés acteurs du système concurrent.
Partage de données
Un problème important lors de la mise en œuvre des systèmes simultanés est le partage des
données entre plusieurs threads ou processus. En fait, le programmeur doit s'assurer que les
verrous protègent les données partagées afin que tous les accès à celles-ci soient sérialisés et
qu'un seul thread ou processus puisse accéder aux données partagées à la fois. Dans le cas où
plusieurs threads ou processus essaient tous d'accéder aux mêmes données partagées, tous,
mais au moins un d'entre eux, ne seraient pas bloqués et resteraient inactifs. En d'autres termes,
nous pouvons dire que nous ne pourrions utiliser qu'un seul processus ou thread à la fois lorsque
le verrouillage est en vigueur. Il peut y avoir des solutions simples pour supprimer les barrières
mentionnées ci-dessus -
import urllib.request
import time
ts = time.time()
req = urllib.request.urlopen('http://www.tutorialspoint.com')
pageHtml = req.read()
te = time.time()
Après avoir exécuté le script ci-dessus, nous pouvons obtenir le temps de récupération de la
page comme indiqué ci-dessous.
Production
Page Fetching Time: 1.0991398811340332 Seconds
Nous pouvons voir que le temps de récupération de la page est supérieur à une seconde.
Maintenant, que se passe-t-il si nous voulons récupérer des milliers de pages Web différentes,
vous pouvez comprendre combien de temps notre réseau prendrait.
Pour avoir plus d'idées sur la distinction entre la concurrence et le parallélisme, considérez les
points suivants -
Ni parallèle ni simultané
Une application ne peut être ni parallèle ni concurrente. Cela signifie qu'il ne fonctionne que sur
une seule tâche à la fois et que la tâche n'est jamais divisée en sous-tâches.
Parallèle et simultané
Une application peut être à la fois parallèle et simultanée, ce qui signifie qu'elle fonctionne à la
fois sur plusieurs tâches à la fois et que la tâche est divisée en sous-tâches pour les exécuter en
parallèle.
Nécessité du parallélisme
Nous pouvons réaliser le parallélisme en répartissant les sous-tâches entre différents cœurs d'un
seul processeur ou entre plusieurs ordinateurs connectés au sein d'un réseau.
Considérez les points importants suivants pour comprendre pourquoi il est nécessaire de réaliser
le parallélisme -
Processeurs monocœur
Les processeurs monocœur sont capables d'exécuter un thread à tout moment. Ces processeurs
utilisentcontext switchingpour stocker toutes les informations nécessaires pour un thread à un
moment précis, puis restaurer les informations ultérieurement. Le mécanisme de changement de
contexte nous aide à progresser sur un certain nombre de threads en une seconde donnée et il
semble que le système travaille sur plusieurs choses.
Les processeurs monocœur présentent de nombreux avantages. Ces processeurs nécessitent
moins d'énergie et il n'y a pas de protocole de communication complexe entre plusieurs cœurs.
D'autre part, la vitesse des processeurs monocœur est limitée et ne convient pas aux
applications plus importantes.
Processeurs multicœurs
Les processeurs multicœurs ont plusieurs unités de traitement indépendantes également
appelées cores.
Ces processeurs n'ont pas besoin de mécanisme de changement de contexte car chaque cœur
contient tout ce dont il a besoin pour exécuter une séquence d'instructions stockées.
Cycle d'extraction-décodage-exécution
Les cœurs des processeurs multicœurs suivent un cycle d'exécution. Ce cycle s'appelle leFetch-
Decode-Executecycle. Cela implique les étapes suivantes -
Récupérer
C'est la première étape du cycle, qui implique la récupération des instructions de la mémoire du
programme.
Décoder
Les instructions récemment récupérées seraient converties en une série de signaux qui
déclencheront d'autres parties du processeur.
Exécuter
C'est la dernière étape dans laquelle les instructions extraites et décodées seraient exécutées.
Le résultat de l'exécution sera stocké dans un registre CPU.
Un avantage ici est que l'exécution dans les processeurs multicœurs est plus rapide que celle
des processeurs monocœur. Il convient aux applications plus importantes. D'autre part, un
protocole de communication complexe entre plusieurs cœurs est un problème. Les cœurs
multiples nécessitent plus de puissance que les processeurs monocœur.
Avantages du SISD
Les avantages de l'architecture SISD sont les suivants -
Cela nécessite moins d'énergie.
Il n'y a pas de problème de protocole de communication complexe entre plusieurs cœurs.
Inconvénients du SISD
Les inconvénients de l'architecture SISD sont les suivants -
La vitesse de l'architecture SISD est limitée, tout comme les processeurs monocœur.
Il ne convient pas aux applications plus importantes.
Avantages de SIMD
Les avantages de l'architecture SIMD sont les suivants -
La même opération sur plusieurs éléments peut être effectuée en utilisant une seule
instruction.
Le débit du système peut être augmenté en augmentant le nombre de cœurs du
processeur.
La vitesse de traitement est supérieure à l'architecture SISD.
Inconvénients de SIMD
Les inconvénients de l'architecture SIMD sont les suivants -
Il existe une communication complexe entre plusieurs cœurs de processeur.
Le coût est plus élevé que l'architecture SISD.
États du fil
Pour comprendre en profondeur la fonctionnalité des threads, nous devons en savoir plus sur le
cycle de vie des threads ou sur les différents états des threads. En règle générale, un thread peut
exister dans cinq états distincts. Les différents états sont indiqués ci-dessous -
Nouveau fil
Un nouveau thread commence son cycle de vie dans le nouvel état. Cependant, à ce stade, il n'a
pas encore démarré et aucune ressource ne lui a été allouée. Nous pouvons dire que ce n'est
qu'une instance d'un objet.
Runnable
Lorsque le thread nouvellement né est démarré, le thread devient exécutable, c'est-à-dire en
attente d'exécution. Dans cet état, il dispose de toutes les ressources mais le planificateur de
tâches n'a toujours pas planifié son exécution.
Fonctionnement
Dans cet état, le thread progresse et exécute la tâche, qui a été choisie par le planificateur de
tâches à exécuter. Maintenant, le thread peut passer soit à l'état mort, soit à l'état non exécutable
/ en attente.
Mort
Un thread exécutable entre dans l'état terminé lorsqu'il termine sa tâche ou s'arrête autrement.
Le diagramme suivant montre le cycle de vie complet d'un thread -
Types de fil
Dans cette section, nous verrons les différents types de threads. Les types sont décrits ci-
dessous -
Dans plusieurs environnements de traitement, chaque processus exécute le même Tous les threads peuvent pa
code mais possède sa propre mémoire et ses propres ressources de fichiers. ouverts, processus enfants.
Si un processus est bloqué, aucun autre processus ne peut s'exécuter tant que le Pendant qu'un thread est b
premier processus n'est pas débloqué. de la même tâche peut s'ex
Plusieurs processus sans utiliser de threads utilisent plus de ressources. Plusieurs processus threadé
Dans plusieurs processus, chaque processus fonctionne indépendamment des autres. Un thread peut lire, écrire o
thread.
S'il y avait un changement dans le processus parent, cela n'affecte pas les processus S'il y avait une modification
enfants. affecter le comportement d
Pour communiquer avec des processus frères, les processus doivent utiliser la Les threads peuvent commu
communication inter-processus. threads de ce processus.
Concept de multithreading
Comme nous l'avons vu précédemment, le multithreading est la capacité d'un processeur à gérer
l'utilisation du système d'exploitation en exécutant plusieurs threads simultanément. L'idée
principale du multithreading est de réaliser le parallélisme en divisant un processus en plusieurs
threads. De manière plus simple, on peut dire que le multithreading est le moyen de réaliser le
multitâche en utilisant le concept de threads.
Le concept de multithreading peut être compris à l'aide de l'exemple suivant.
Exemple
Supposons que nous exécutions un processus. Le processus pourrait être d'ouvrir MS Word pour
écrire quelque chose. Dans un tel processus, un thread sera assigné pour ouvrir MS word et un
autre thread sera nécessaire pour écrire. Maintenant, supposons que si nous voulons éditer
quelque chose, un autre thread sera nécessaire pour effectuer la tâche d'édition et ainsi de suite.
Le diagramme suivant nous aide à comprendre comment plusieurs threads existent en mémoire -
Nous pouvons voir dans le diagramme ci-dessus que plusieurs threads peuvent exister dans un
processus où chaque thread contient son propre jeu de registres et des variables locales. En
dehors de cela, tous les threads d'un processus partagent des variables globales.
Avantages du multithreading
Voyons maintenant quelques avantages du multithreading. Les avantages sont les suivants -
Speed of communication - Le multithreading améliore la vitesse de calcul car chaque
cœur ou processeur gère des threads séparés simultanément.
Program remains responsive - Il permet à un programme de rester réactif car un thread
attend l'entrée et un autre exécute une interface graphique en même temps.
Access to global variables - En multithreading, tous les threads d'un processus
particulier peuvent accéder aux variables globales et s'il y a un changement dans la
variable globale, il est également visible pour les autres threads.
Utilization of resources - L'exécution de plusieurs threads dans chaque programme
permet une meilleure utilisation du processeur et le temps d'inactivité du processeur
diminue.
Sharing of data - Il n'y a pas besoin d'espace supplémentaire pour chaque thread car les
threads d'un programme peuvent partager les mêmes données.
Inconvénients du multithreading
Voyons maintenant quelques inconvénients du multithreading. Les inconvénients sont les
suivants -
Not suitable for single processor system - Le multithreading a du mal à obtenir des
performances en termes de vitesse de calcul sur un système à processeur unique par
rapport aux performances sur un système multiprocesseur.
Issue of security - Comme nous savons que tous les threads d'un programme partagent
les mêmes données, il y a donc toujours un problème de sécurité car tout thread inconnu
peut changer les données.
Increase in complexity - Le multithreading peut augmenter la complexité du programme
et le débogage devient difficile.
Lead to deadlock state - Le multithreading peut conduire le programme à un risque
potentiel d'atteindre l'état de blocage.
Synchronization required- La synchronisation est nécessaire pour éviter l'exclusion
mutuelle. Cela conduit à une plus grande utilisation de la mémoire et du processeur.
Implémentation de threads
Dans ce chapitre, nous allons apprendre à implémenter des threads en Python.
Module <_thread>
Dans la version précédente de Python, nous avions le <thread>module mais il a été considéré
comme "obsolète" pendant assez longtemps. Les utilisateurs ont été encouragés à utiliser
le<threading>module à la place. Par conséquent, dans Python 3, le module "thread" n'est plus
disponible. Il a été renommé en "<_thread>"pour les incompatibilités vers l'arrière dans Python3.
Pour générer un nouveau fil à l'aide du <_thread> module, nous devons appeler
le start_new_threadméthode de celui-ci. Le fonctionnement de cette méthode peut être compris
à l'aide de la syntaxe suivante -
Ici -
args est un tuple d'arguments
kwargs est un dictionnaire facultatif d'arguments de mots clés
Si nous voulons appeler une fonction sans passer d'argument, nous devons utiliser un tuple vide
d'arguments dans args.
Cet appel de méthode retourne immédiatement, le thread enfant démarre et appelle la fonction
avec la liste passée, le cas échéant, d'arguments. Le thread se termine au fur et à mesure que la
fonction retourne.
Exemple
Voici un exemple de génération d'un nouveau thread à l'aide de <_thread>module. Nous
utilisons ici la méthode start_new_thread ().
import _thread
import time
def print_time( threadName, delay):
count = 0
while count < 5:
time.sleep(delay)
count += 1
print ("%s: %s" % ( threadName, time.ctime(time.time()) ))
try:
_thread.start_new_thread( print_time, ("Thread-1", 2, ) )
_thread.start_new_thread( print_time, ("Thread-2", 4, ) )
except:
print ("Error: unable to start thread")
while 1:
pass
Production
La sortie suivante nous aidera à comprendre la génération de nouveaux threads à l'aide
du <_thread> module.
Module <threading>
le <threading>module implémente de manière orientée objet et traite chaque thread comme un
objet. Par conséquent, il fournit une prise en charge beaucoup plus puissante et de haut niveau
des threads que le module <_thread>. Ce module est inclus avec Python 2.4.
Méthodes supplémentaires dans le
module <threading>
le <threading> module comprend toutes les méthodes du <_thread>module mais il fournit
également des méthodes supplémentaires. Les méthodes supplémentaires sont les suivantes -
threading.activeCount() - Cette méthode renvoie le nombre d'objets thread qui sont
actifs
threading.currentThread() - Cette méthode renvoie le nombre d'objets de thread dans le
contrôle de thread de l'appelant.
threading.enumerate() - Cette méthode renvoie une liste de tous les objets de thread
actuellement actifs.
Pour implémenter le threading, le <threading> module a le Thread classe qui fournit les
méthodes suivantes -
o run() - La méthode run () est le point d'entrée d'un thread.
o start() - La méthode start () démarre un thread en appelant la méthode run.
o join([time]) - Le join () attend la fin des threads.
o isAlive() - La méthode isAlive () vérifie si un thread est toujours en cours
d'exécution.
o getName() - La méthode getName () renvoie le nom d'un thread.
o setName() - La méthode setName () définit le nom d'un thread.
Exemple
Considérez cet exemple pour apprendre à générer un nouveau thread en utilisant
le <threading> module.
import threading
import time
exitFlag = 0
class myThread (threading.Thread):
def __init__(self, threadID, name, counter):
threading.Thread.__init__(self)
self.threadID = threadID
self.name = name
self.counter = counter
def run(self):
print ("Starting " + self.name)
print_time(self.name, self.counter, 5)
print ("Exiting " + self.name)
def print_time(threadName, delay, counter):
while counter:
if exitFlag:
threadName.exit()
time.sleep(delay)
print ("%s: %s" % (threadName, time.ctime(time.time())))
counter -= 1
thread1.start()
thread2.start()
thread1.join()
thread2.join()
print ("Exiting Main Thread")
Starting Thread-1
Starting Thread-2
Production
Maintenant, considérez la sortie suivante -
import threading
import time
Step 2 - Définissez une fonction qui sera appelée lors de la création d'un thread.
def thread_states():
print("Thread entered in running state")
Step 3 - Nous utilisons la méthode sleep () du module de temps pour faire attendre notre thread
pendant 2 secondes.
time.sleep(2)
Step 4 - Maintenant, nous créons un thread nommé T1, qui prend l'argument de la fonction
définie ci-dessus.
T1 = threading.Thread(target=thread_states)
Step 5- Maintenant, avec l'aide de la fonction start (), nous pouvons démarrer notre thread. Il
produira le message que nous avons défini lors de la définition de la fonction.
T1.start()
Thread entered in running state
Step 6 - Maintenant, nous pouvons enfin tuer le thread avec la méthode join () une fois son
exécution terminée.
T1.join()
import threading
import time
import random
def Thread_execution(i):
print("Execution of Thread {} started\n".format(i))
sleepTime = random.randint(1,4)
time.sleep(sleepTime)
print("Execution of Thread {} finished".format(i))
for i in range(4):
thread = threading.Thread(target=Thread_execution, args=(i,))
thread.start()
print("Active Threads:" , threading.enumerate())
Production
Execution of Thread 0 started
Active Threads:
[<_MainThread(MainThread, started 6040)>,
<HistorySavingThread(IPythonHistorySavingThread, started 5968)>,
<Thread(Thread-3576, started 3932)>]
import threading
import time
def nondaemonThread():
print("starting my thread")
time.sleep(8)
print("ending my thread")
def daemonThread():
while True:
print("Hello")
time.sleep(2)
if __name__ == '__main__':
nondaemonThread = threading.Thread(target = nondaemonThread)
daemonThread = threading.Thread(target = daemonThread)
daemonThread.setDaemon(True)
daemonThread.start()
nondaemonThread.start()
Hello
starting my thread
Hello
Hello
Hello
Hello
ending my thread
Hello
Hello
Hello
Hello
Hello
Pour clarifier les choses, supposons que deux threads ou plus essaient d'ajouter l'objet dans la
liste en même temps. Cet acte ne peut pas conduire à une fin réussie car soit il supprimera un ou
tous les objets, soit il corrompra complètement l'état de la liste. Ici, le rôle de la synchronisation
est qu'un seul thread à la fois peut accéder à la liste.
Condition de course
C'est l'un des problèmes majeurs de la programmation simultanée. L'accès simultané aux
ressources partagées peut conduire à une condition de concurrence. Une condition de
concurrence peut être définie comme la survenance d'une condition lorsque deux ou plusieurs
threads peuvent accéder aux données partagées et ensuite essayer de modifier sa valeur en
même temps. Pour cette raison, les valeurs des variables peuvent être imprévisibles et varier en
fonction des horaires des changements de contexte des processus.
Exemple
Considérez cet exemple pour comprendre le concept de condition de race -
Step 1 - Dans cette étape, nous devons importer le module de threading -
import threading
Step 2 - Maintenant, définissez une variable globale, disons x, avec sa valeur comme 0 -
x = 0
def increment_global():
global x
x += 1
Step 4 - Dans cette étape, nous définirons le taskofThread()function, qui appellera la fonction
increment_global () un certain nombre de fois; pour notre exemple, c'est 50000 fois -
def taskofThread():
for _ in range(50000):
increment_global()
Step 5- Maintenant, définissez la fonction main () dans laquelle les threads t1 et t2 sont créés.
Les deux seront démarrés à l'aide de la fonction start () et attendront de terminer leur travail à
l'aide de la fonction join ().
def main():
global x
x = 0
t1 = threading.Thread(target= taskofThread)
t2 = threading.Thread(target= taskofThread)
t1.start()
t2.start()
t1.join()
t2.join()
Step 6- Maintenant, nous devons donner la plage comme pour le nombre d'itérations que nous
voulons appeler la fonction main (). Ici, nous l'appelons 5 fois.
if __name__ == "__main__":
for i in range(5):
main()
print("x = {1} after Iteration {0}".format(i,x))
Dans la sortie montrée ci-dessous, nous pouvons voir l'effet de la condition de concurrence
comme la valeur de x après chaque itération est attendue 100000. Cependant, il y a beaucoup
de variation dans la valeur. Cela est dû à l'accès simultané des threads à la variable globale
partagée x.
Production
x = 100000 after Iteration 0
x = 54034 after Iteration 1
x = 80230 after Iteration 2
x = 93602 after Iteration 3
x = 93289 after Iteration 4
Acquérir () méthode
Cette méthode est utilisée pour acquérir, c'est-à-dire bloquer un verrou. Un verrou peut être
bloquant ou non bloquant selon la valeur vraie ou fausse suivante -
With value set to True - Si la méthode Acquérir () est appelée avec True, qui est
l'argument par défaut, alors l'exécution du thread est bloquée jusqu'à ce que le verrou
soit déverrouillé.
With value set to False - Si la méthode Acquérir () est invoquée avec False, ce qui n'est
pas l'argument par défaut, alors l'exécution du thread n'est pas bloquée tant qu'elle n'est
pas mise à true, c'est-à-dire tant qu'elle n'est pas verrouillée.
méthode release ()
Cette méthode est utilisée pour libérer un verrou. Voici quelques tâches importantes liées à cette
méthode -
Si un verrou est verrouillé, le release()la méthode le déverrouillerait. Son travail est de
permettre à exactement un thread de continuer si plusieurs threads sont bloqués et
attendent que le verrou soit déverrouillé.
Cela soulèvera un ThreadError si le verrou est déjà déverrouillé.
Maintenant, nous pouvons réécrire le programme ci-dessus avec la classe de verrouillage et ses
méthodes pour éviter la condition de concurrence. Nous devons définir la méthode taskofThread
() avec l'argument lock, puis utiliser les méthodes Acquérir () et Release () pour bloquer et non
bloquer les verrous afin d'éviter les conditions de concurrence.
Exemple
Voici un exemple de programme python pour comprendre le concept de verrous pour gérer les
conditions de concurrence -
import threading
x = 0
def increment_global():
global x
x += 1
def taskofThread(lock):
for _ in range(50000):
lock.acquire()
increment_global()
lock.release()
def main():
global x
x = 0
lock = threading.Lock()
t1 = threading.Thread(target = taskofThread, args = (lock,))
t2 = threading.Thread(target = taskofThread, args = (lock,))
t1.start()
t2.start()
t1.join()
t2.join()
if __name__ == "__main__":
for i in range(5):
main()
print("x = {1} after Iteration {0}".format(i,x))
La sortie suivante montre que l'effet de la condition de concurrence est négligé; car la valeur de
x, après chaque & chaque itération, est maintenant de 100000, ce qui correspond aux attentes
de ce programme.
Production
x = 100000 after Iteration 0
x = 100000 after Iteration 1
x = 100000 after Iteration 2
x = 100000 after Iteration 3
x = 100000 after Iteration 4
Exemple
Le programme Python suivant nous aidera à trouver une solution au problème du philosophe de
la restauration -
import threading
import random
import time
class DiningPhilosopher(threading.Thread):
running = True
def run(self):
while(self.running):
time.sleep( random.uniform(3,13))
print ('%s is hungry.' % self.name)
self.dine()
def dine(self):
fork1, fork2 = self.Leftfork, self.Rightfork
while self.running:
fork1.acquire(True)
locked = fork2.acquire(False)
if locked: break
fork1.release()
print ('%s swaps forks' % self.name)
fork1, fork2 = fork2, fork1
else:
return
self.dining()
fork2.release()
fork1.release()
def dining(self):
print ('%s starts eating '% self.name)
time.sleep(random.uniform(1,10))
print ('%s finishes eating and now thinking.' % self.name)
def Dining_Philosophers():
forks = [threading.Lock() for n in range(5)]
philosopherNames = ('1st','2nd','3rd','4th', '5th')
random.seed()
DiningPhilosopher.running = True
for p in philosophers: p.start()
time.sleep(30)
DiningPhilosopher.running = False
print (" It is finishing.")
Dining_Philosophers()
Intercommunication de threads
Dans la vraie vie, si une équipe de personnes travaille sur une tâche commune, il devrait y avoir
une communication entre elles pour terminer la tâche correctement. La même analogie
s'applique également aux threads. En programmation, pour réduire le temps idéal du processeur,
nous créons plusieurs threads et assignons différentes sous-tâches à chaque thread. Par
conséquent, il doit y avoir une installation de communication et ils doivent interagir les uns avec
les autres pour terminer le travail de manière synchronisée.
Considérez les points importants suivants liés à l'intercommunication de thread -
No performance gain - Si nous ne pouvons pas parvenir à une communication correcte
entre les threads et les processus, les gains de performances de la concurrence et du
parallélisme ne sont d'aucune utilité.
Accomplish task properly - Sans mécanisme d'intercommunication approprié entre les
threads, la tâche assignée ne peut pas être exécutée correctement.
More efficient than inter-process communication - La communication inter-thread est
plus efficace et plus facile à utiliser que la communication inter-processus car tous les
threads d'un processus partagent le même espace d'adressage et n'ont pas besoin
d'utiliser la mémoire partagée.
Ensembles
Pour utiliser la structure de données set de manière thread-safe, nous devons étendre la classe
set pour implémenter notre propre mécanisme de verrouillage.
Exemple
Voici un exemple Python d'extension de la classe -
class extend_class(set):
def __init__(self, *args, **kwargs):
self._lock = Lock()
super(extend_class, self).__init__(*args, **kwargs)
Dans l'exemple ci-dessus, un objet de classe nommé extend_class a été défini qui est hérité du
Python set class. Un objet de verrouillage est créé dans le constructeur de cette classe.
Maintenant, il y a deux fonctions -add() et delete(). Ces fonctions sont définies et sont thread-
safe. Ils comptent tous deux sur lesuper fonctionnalité de classe avec une exception clé.
Décorateur
C'est une autre méthode clé pour la communication thread-safe est l'utilisation de décorateurs.
Exemple
Prenons un exemple Python qui montre comment utiliser les décorateurs & mminus;
def lock_decorator(method):
class Decorator_class(set):
def __init__(self, *args, **kwargs):
self._lock = Lock()
super(Decorator_class, self).__init__(*args, **kwargs)
@lock_decorator
def add(self, *args, **kwargs):
return super(Decorator_class, self).add(elem)
@lock_decorator
def delete(self, *args, **kwargs):
return super(Decorator_class, self).delete(elem)
Dans l'exemple ci-dessus, une méthode décoratrice nommée lock_decorator a été définie,
héritée de la classe de méthode Python. Ensuite, un objet de verrouillage est créé dans le
constructeur de cette classe. Maintenant, il y a deux fonctions - add () et delete (). Ces fonctions
sont définies et sont thread-safe. Ils s'appuient tous deux sur des fonctionnalités de grande
classe à une exception près.
Listes
La structure des données de la liste est sécurisée pour les threads, rapide et simple pour un
stockage temporaire en mémoire. En Cpython, le GIL protège contre l'accès simultané à ceux-ci.
Comme nous avons appris que les listes sont thread-safe, mais qu'en est-il des données qu'elles
contiennent. En fait, les données de la liste ne sont pas protégées. Par
exemple,L.append(x)n'est pas garanti de renvoyer le résultat attendu si un autre thread essaie
de faire la même chose. C'est parce que, bien queappend() est une opération atomique et
thread-safe, mais l'autre thread essaie de modifier les données de la liste de manière
concurrente, nous pouvons donc voir les effets secondaires des conditions de concurrence sur la
sortie.
Pour résoudre ce type de problème et modifier en toute sécurité les données, nous devons
implémenter un mécanisme de verrouillage approprié, ce qui garantit en outre que plusieurs
threads ne peuvent pas potentiellement rencontrer des conditions de concurrence. Pour
implémenter un mécanisme de verrouillage approprié, nous pouvons étendre la classe comme
nous l'avons fait dans les exemples précédents.
Certaines autres opérations atomiques sur les listes sont les suivantes -
L.append(x)
L1.extend(L2)
x = L[i]
x = L.pop()
L1[i:j] = L2
L.sort()
x = y
x.field = y
D[x] = y
D1.update(D2)
D.keys()
Ici -
L, L1, L2 sont tous des listes
D, D1, D2 sont des dictionnaires
x, y sont des objets
je, j sont des entiers
Files d'attente
Si les données de la liste ne sont pas protégées, nous pourrions être confrontés aux
conséquences. Nous pouvons obtenir ou supprimer des données erronées, des conditions de
course. C'est pourquoi il est recommandé d'utiliser la structure de données de file d'attente. Un
exemple concret de file d'attente peut être une route à sens unique à voie unique, où le véhicule
entre en premier, sort en premier. D'autres exemples concrets peuvent être vus des files d'attente
aux guichets et aux arrêts de bus.
Les files d'attente sont par défaut une structure de données thread-safe et nous n'avons pas à
nous soucier de l'implémentation d'un mécanisme de verrouillage complexe. Python nous fournit
le module pour utiliser différents types de files d'attente dans notre application.
Exemple
Voici un programme Python pour l'implémentation de la file d'attente FIFO avec un seul thread -
import queue
q = queue.Queue()
for i in range(8):
q.put("item-" + str(i))
Production
item-0 item-1 item-2 item-3 item-4 item-5 item-6 item-7
La sortie montre que le programme ci-dessus utilise un seul thread pour illustrer que les éléments
sont supprimés de la file d'attente dans le même ordre qu'ils sont insérés.
Exemple
Voici un programme Python pour l'implémentation de la file d'attente FIFO avec plusieurs threads
import threading
import queue
import random
import time
def myqueue(queue):
while not queue.empty():
item = queue.get()
if item is None:
break
print("{} removed {} from the queue".format(threading.current_thread(),
item))
queue.task_done()
time.sleep(2)
q = queue.Queue()
for i in range(5):
q.put(i)
threads = []
for i in range(4):
thread = threading.Thread(target=myqueue, args=(q,))
thread.start()
threads.append(thread)
for thread in threads:
thread.join()
Production
<Thread(Thread-3654, started 5044)> removed 0 from the queue
<Thread(Thread-3655, started 3144)> removed 1 from the queue
<Thread(Thread-3656, started 6996)> removed 2 from the queue
<Thread(Thread-3657, started 2672)> removed 3 from the queue
<Thread(Thread-3654, started 5044)> removed 4 from the queue
Exemple
Voici un programme Python pour l'implémentation de la file d'attente LIFO avec un seul thread -
import queue
q = queue.LifoQueue()
for i in range(8):
q.put("item-" + str(i))
La sortie montre que le programme ci-dessus utilise un seul thread pour illustrer que les éléments
sont supprimés de la file d'attente dans l'ordre inverse de leur insertion.
Exemple
Voici un programme Python pour l'implémentation de la file d'attente LIFO avec plusieurs threads
-
import threading
import queue
import random
import time
def myqueue(queue):
while not queue.empty():
item = queue.get()
if item is None:
break
print("{} removed {} from the
queue".format(threading.current_thread(), item))
queue.task_done()
time.sleep(2)
q = queue.LifoQueue()
for i in range(5):
q.put(i)
threads = []
for i in range(4):
thread = threading.Thread(target=myqueue, args=(q,))
thread.start()
threads.append(thread)
for thread in threads:
thread.join()
Production
<Thread(Thread-3882, started 4928)> removed 4 from the queue
<Thread(Thread-3883, started 4364)> removed 3 from the queue
<Thread(Thread-3884, started 6908)> removed 2 from the queue
<Thread(Thread-3885, started 3584)> removed 1 from the queue
<Thread(Thread-3882, started 4928)> removed 0 from the queue
Exemple
Considérez le programme Python suivant pour l'implémentation de la file d'attente prioritaire avec
un seul thread -
import queue as Q
p_queue = Q.PriorityQueue()
p_queue.put((2, 'Urgent'))
p_queue.put((1, 'Most Urgent'))
p_queue.put((10, 'Nothing important'))
prio_queue.put((5, 'Important'))
Production
1 – Most Urgent
2 - Urgent
5 - Important
10 – Nothing important
Dans la sortie ci-dessus, nous pouvons voir que la file d'attente a stocké les éléments en fonction
de la priorité - moins de valeur a une priorité élevée.
Exemple
Le programme Python suivant aide à l'implémentation de la file d'attente prioritaire avec plusieurs
threads -
import threading
import queue
import random
import time
def myqueue(queue):
while not queue.empty():
item = queue.get()
if item is None:
break
print("{} removed {} from the
queue".format(threading.current_thread(), item))
queue.task_done()
time.sleep(1)
q = queue.PriorityQueue()
for i in range(5):
q.put(i,1)
for i in range(5):
q.put(i,1)
threads = []
for i in range(2):
thread = threading.Thread(target=myqueue, args=(q,))
thread.start()
threads.append(thread)
for thread in threads:
thread.join()
Production
<Thread(Thread-4939, started 2420)> removed 0 from the queue
<Thread(Thread-4940, started 3284)> removed 0 from the queue
<Thread(Thread-4939, started 2420)> removed 1 from the queue
<Thread(Thread-4940, started 3284)> removed 1 from the queue
<Thread(Thread-4939, started 2420)> removed 2 from the queue
<Thread(Thread-4940, started 3284)> removed 2 from the queue
<Thread(Thread-4939, started 2420)> removed 3 from the queue
<Thread(Thread-4940, started 3284)> removed 3 from the queue
<Thread(Thread-4939, started 2420)> removed 4 from the queue
<Thread(Thread-4940, started 3284)> removed 4 from the queue
Pourquoi tester?
Avant de plonger dans la discussion sur l'importance des tests, nous devons savoir ce que sont
les tests. En termes généraux, les tests sont une technique permettant de déterminer si quelque
chose fonctionne bien. D'un autre côté, en particulier si nous parlons de programmes
informatiques ou de logiciels, le test est la technique d'accès à la fonctionnalité d'un logiciel.
Dans cette section, nous discuterons de l'importance des tests logiciels. Dans le développement
de logiciels, il doit y avoir une double vérification avant la publication du logiciel au client. C'est
pourquoi il est très important de tester le logiciel par une équipe de test expérimentée. Tenez
compte des points suivants pour comprendre l'importance des tests logiciels -
Expérience utilisateur
Une autre partie la plus importante de toute entreprise est l'expérience des utilisateurs de ce
produit. Seuls les tests peuvent garantir que l'utilisateur final trouve qu'il est simple et facile
d'utiliser le produit.
Que tester?
Il est toujours recommandé d'avoir une connaissance appropriée de ce qui doit être testé. Dans
cette section, nous allons d'abord comprendre le principal motif du testeur lors du test d'un
logiciel. La couverture de code, c'est-à-dire le nombre de lignes de code que notre suite de tests
atteint, lors des tests, doit être évitée. C'est parce que, lors des tests, se concentrer uniquement
sur le nombre de lignes de codes n'ajoute aucune valeur réelle à notre système. Il peut rester
quelques bogues, qui se reflètent plus tard à un stade ultérieur, même après le déploiement.
Tenez compte des points importants suivants relatifs à ce qu'il faut tester -
Nous devons nous concentrer sur le test de la fonctionnalité du code plutôt que sur la
couverture du code.
Nous devons d'abord tester les parties les plus importantes du code, puis passer aux
parties les moins importantes du code. Cela fera certainement gagner du temps.
Le testeur doit avoir une multitude de tests différents qui peuvent pousser le logiciel à ses
limites.
Stratégies de test
La stratégie de test est également appelée approche de test. La stratégie définit la manière dont
les tests seraient effectués. L'approche de test a deux techniques -
Proactif
Une approche dans laquelle le processus de conception de test est lancé le plus tôt possible afin
de trouver et de corriger les défauts avant la création de la construction.
Réactif
Une approche dans laquelle les tests ne commencent qu'à la fin du processus de
développement.
Avant d'appliquer une stratégie ou une approche de test sur un programme python, nous devons
avoir une idée de base sur le type d'erreurs qu'un programme logiciel peut avoir. Les erreurs sont
les suivantes -
Erreurs de syntaxe
Pendant le développement du programme, il peut y avoir de nombreuses petites erreurs. Les
erreurs sont principalement dues à des fautes de frappe. Par exemple, deux points manquants
ou une mauvaise orthographe d'un mot-clé, etc. Ces erreurs sont dues à une erreur de syntaxe
du programme et non de logique. Par conséquent, ces erreurs sont appelées erreurs
syntaxiques.
Erreurs sémantiques
Les erreurs sémantiques sont également appelées erreurs logiques. S'il y a une erreur logique ou
sémantique dans le programme logiciel, l'instruction sera compilée et exécutée correctement
mais elle ne donnera pas la sortie souhaitée car la logique n'est pas correcte.
Test unitaire
C'est l'une des stratégies de test les plus utilisées pour tester les programmes python. Cette
stratégie est utilisée pour tester les unités ou les composants du code. Par unités ou
composants, nous entendons des classes ou des fonctions du code. Les tests unitaires
simplifient les tests de grands systèmes de programmation en testant de «petites» unités. Avec
l'aide du concept ci-dessus, les tests unitaires peuvent être définis comme une méthode dans
laquelle des unités individuelles de code source sont testées pour déterminer si elles renvoient la
sortie souhaitée.
Dans nos sections suivantes, nous découvrirons les différents modules Python pour les tests
unitaires.
module unittest
Le tout premier module pour les tests unitaires est le module unittest. Il est inspiré de JUnit et
inclus par défaut dans Python3.6. Il prend en charge l'automatisation des tests, le partage du
code de configuration et d'arrêt pour les tests, l'agrégation des tests en collections et
l'indépendance des tests par rapport au cadre de reporting.
Voici quelques concepts importants pris en charge par le module unittest
Fixation de texte
Il est utilisé pour configurer un test afin qu'il puisse être exécuté avant de démarrer le test et
démonté après la fin du test. Cela peut impliquer la création d'une base de données temporaire,
de répertoires, etc. nécessaires avant de démarrer le test.
Cas de test
Le cas de test vérifie si une réponse requise provient de l'ensemble spécifique d'entrées ou non.
Le module unittest comprend une classe de base nommée TestCase qui peut être utilisée pour
créer de nouveaux cas de test. Il comprend deux méthodes par défaut -
setUp()- une méthode de crochet pour installer le dispositif d'essai avant de l'exercer.
Ceci est appelé avant d'appeler les méthodes de test implémentées.
tearDown( - une méthode hook pour déconstruire le fixture de classe après avoir exécuté
tous les tests de la classe.
Suite de tests
Il s'agit d'un ensemble de suites de tests, de cas de test ou les deux.
Testeur
Il contrôle l'exécution des cas de test ou des combinaisons et fournit le résultat à l'utilisateur. Il
peut utiliser une interface graphique ou une simple interface de texte pour fournir le résultat.
Example
Le programme Python suivant utilise le module unittest pour tester un module nommé Fibonacci.
Le programme aide à calculer la série de Fibonacci d'un nombre. Dans cet exemple, nous avons
créé une classe nommée Fibo_test, pour définir les cas de test en utilisant différentes méthodes.
Ces méthodes sont héritées de unittest.TestCase. Nous utilisons deux méthodes par défaut -
setUp () et tearDown (). Nous définissons également la méthode testfibocal. Le nom du test doit
commencer par le test des lettres. Dans le dernier bloc, unittest.main () fournit une interface de
ligne de commande au script de test.
import unittest
def fibonacci(n):
a, b = 0, 1
for i in range(n):
a, b = b, a + b
return a
class Fibo_Test(unittest.TestCase):
def setUp(self):
print("This is run before our tests would be executed")
def tearDown(self):
print("This is run after the completion of execution of our tests")
def testfibocal(self):
self.assertEqual(fib(0), 0)
self.assertEqual(fib(1), 1)
self.assertEqual(fib(5), 5)
self.assertEqual(fib(10), 55)
self.assertEqual(fib(20), 6765)
if __name__ == "__main__":
unittest.main()
Lorsqu'il est exécuté à partir de la ligne de commande, le script ci-dessus produit une sortie qui
ressemble à ceci -
Production
This runs before our tests would be executed.
This runs after the completion of execution of our tests.
.
----------------------------------------------------------------------
Ran 1 test in 0.006s
OK
Maintenant, pour être plus clair, nous changeons notre code qui a aidé à définir le module
Fibonacci.
Considérez le bloc de code suivant comme exemple -
def fibonacci(n):
a, b = 0, 1
for i in range(n):
a, b = b, a + b
return a
def fibonacci(n):
a, b = 1, 1
for i in range(n):
a, b = b, a + b
return a
Maintenant, après avoir exécuté le script avec le code modifié, nous obtiendrons la sortie
suivante -
FAILED (failures = 1)
La sortie ci-dessus montre que le module n'a pas réussi à fournir la sortie souhaitée.
Module Docktest
Le module docktest aide également aux tests unitaires. Il est également livré pré-emballé avec
python. Il est plus facile à utiliser que le module unittest. Le module unittest est plus adapté aux
tests complexes. Pour utiliser le module doctest, nous devons l'importer. La docstring de la
fonction correspondante doit avoir une session python interactive avec leurs sorties.
Si tout va bien dans notre code, il n'y aura pas de sortie du module docktest; sinon, il fournira la
sortie.
Exemple
L'exemple Python suivant utilise le module docktest pour tester un module nommé Fibonacci, qui
aide à calculer la série Fibonacci d'un nombre.
import doctest
def fibonacci(n):
"""
Calculates the Fibonacci number
>>> fibonacci(0)
0
>>> fibonacci(1)
1
>>> fibonacci(10)
55
>>> fibonacci(20)
6765
>>>
"""
a, b = 1, 1
for i in range(n):
a, b = b, a + b
return a
if __name__ == "__main__":
doctest.testmod()
Nous pouvons voir que la docstring de la fonction correspondante nommée fib avait une session
python interactive avec les sorties. Si notre code est correct, il n'y aurait pas de sortie du module
doctest. Mais pour voir comment cela fonctionne, nous pouvons l'exécuter avec l'option –v.
Maintenant, nous allons changer le code qui a aidé à définir le module Fibonacci
Considérez le bloc de code suivant comme exemple -
def fibonacci(n):
a, b = 0, 1
for i in range(n):
a, b = b, a + b
return a
def fibonacci(n):
a, b = 1, 1
for i in range(n):
a, b = b, a + b
return a
Après avoir exécuté le script même sans l'option –v, avec le code modifié, nous obtiendrons la
sortie comme indiqué ci-dessous.
Production
(base) D:\ProgramData>python dock_test.py
**********************************************************************
File "unitg.py", line 6, in __main__.fibonacci
Failed example:
fibonacci(0)
Expected:
0
Got:
1
**********************************************************************
File "unitg.py", line 10, in __main__.fibonacci
Failed example:
fibonacci(10)
Expected:
55
Got:
89
**********************************************************************
File "unitg.py", line 12, in __main__.fibonacci
Failed example:
fibonacci(20)
Expected:
6765
Got:
10946
**********************************************************************
1 items had failures:
3 of 4 in __main__.fibonacci
***Test Failed*** 3 failures.
Nous pouvons voir dans la sortie ci-dessus que trois tests ont échoué.
Débogueur Python
Le débogueur Python ou le pdbfait partie de la bibliothèque standard Python. C'est un bon outil
de secours pour traquer les bogues difficiles à trouver et nous permet de corriger le code
défectueux rapidement et de manière fiable. Les éléments suivants sont les deux tâches les plus
importantes dupdp débogueur -
Cela nous permet de vérifier les valeurs des variables lors de l'exécution.
Nous pouvons également parcourir le code et définir des points d'arrêt.
Nous pouvons travailler avec pdb des deux manières suivantes -
Par la ligne de commande; cela s'appelle également le débogage post-mortem.
En exécutant pdb de manière interactive.
import pdb;
pdb.set_trace()
Considérez les commandes suivantes pour travailler avec pdb via la ligne de commande.
h(help)
d(down)
u(up)
b(break)
cl(clear)
l(list))
n(next))
c(continue)
s(step)
r(return))
b(break)
Voici une démonstration de la commande h (help) du débogueur Python -
import pdb
pdb.set_trace()
--Call--
>d:\programdata\lib\site-packages\ipython\core\
displayhook.py(247)__call__()
-> def __call__(self, result = None):
(Pdb) h
Exemple
Tout en travaillant avec le débogueur Python, nous pouvons définir le point d'arrêt n'importe où
dans le script en utilisant les lignes suivantes -
import pdb;
pdb.set_trace()
Après avoir défini le point d'arrêt, nous pouvons exécuter le script normalement. Le script
s'exécutera jusqu'à un certain point; jusqu'à l'endroit où une ligne a été définie. Prenons
l'exemple suivant où nous exécuterons le script en utilisant les lignes mentionnées ci-dessus à
différents endroits du script -
import pdb;
a = "aaa"
pdb.set_trace()
b = "bbb"
c = "ccc"
final = a + b + c
print (final)
Lorsque le script ci-dessus est exécuté, il exécutera le programme jusqu'à ce que a = «aaa»,
nous pouvons le vérifier dans la sortie suivante.
Production
--Return--
> <ipython-input-7-8a7d1b5cc854>(3)<module>()->None
-> pdb.set_trace()
(Pdb) p a
'aaa'
(Pdb) p b
*** NameError: name 'b' is not defined
(Pdb) p c
*** NameError: name 'c' is not defined
Après avoir utilisé la commande 'p (print)' dans pdb, ce script n'imprime que 'aaa'. Ceci est suivi
d'une erreur car nous avons défini le point d'arrêt jusqu'à a = "aaa".
De même, nous pouvons exécuter le script en modifiant les points d'arrêt et voir la différence
dans la sortie -
import pdb
a = "aaa"
b = "bbb"
c = "ccc"
pdb.set_trace()
final = a + b + c
print (final)
Production
--Return--
> <ipython-input-9-a59ef5caf723>(5)<module>()->None
-> pdb.set_trace()
(Pdb) p a
'aaa'
(Pdb) p b
'bbb'
(Pdb) p c
'ccc'
(Pdb) p final
*** NameError: name 'final' is not defined
(Pdb) exit
Dans le script suivant, nous définissons le point d'arrêt dans la dernière ligne du programme -
import pdb
a = "aaa"
b = "bbb"
c = "ccc"
final = a + b + c
pdb.set_trace()
print (final)
--Return--
> <ipython-input-11-8019b029997d>(6)<module>()->None
-> pdb.set_trace()
(Pdb) p a
'aaa'
(Pdb) p b
'bbb'
(Pdb) p c
'ccc'
(Pdb) p final
'aaabbbccc'
(Pdb)
Exemple
Dans le script Python suivant, nous importons le timeit module, qui mesure en outre le temps
nécessaire pour exécuter deux fonctions - functionA et functionB -
import timeit
import time
def functionA():
print("Function A starts the execution:")
print("Function A completes the execution:")
def functionB():
print("Function B starts the execution")
print("Function B completes the execution")
start_time = timeit.default_timer()
functionA()
print(timeit.default_timer() - start_time)
start_time = timeit.default_timer()
functionB()
print(timeit.default_timer() - start_time)
Après avoir exécuté le script ci-dessus, nous obtiendrons le temps d'exécution des deux
fonctions comme indiqué ci-dessous.
Production
Function A starts the execution:
Function A completes the execution:
0.0014599495514175942
Function B starts the execution
Function B completes the execution
0.0017024724827479076
import random
import time
def timer_func(func):
@timer_func
def Myfunction():
for x in range(5):
sleep_time = random.choice(range(1,3))
time.sleep(sleep_time)
if __name__ == '__main__':
Myfunction()
Le script python ci-dessus aide à importer des modules de temps aléatoires. Nous avons créé la
fonction décoratrice timer_func (). Cela a la fonction function_timer () à l'intérieur. Maintenant, la
fonction imbriquée saisira le temps avant d'appeler la fonction passée. Ensuite, il attend le retour
de la fonction et saisit l'heure de fin. De cette façon, nous pouvons enfin faire en sorte que le
script python affiche l'heure d'exécution. Le script générera la sortie comme indiqué ci-dessous.
Production
Myfunction took 8.000457763671875 seconds to complete its execution.
Exemple
def increment_global():
global x
x += 1
def taskofThread(lock):
for _ in range(50000):
lock.acquire()
increment_global()
lock.release()
def main():
global x
x = 0
lock = threading.Lock()
t1 = threading.Thread(target=taskofThread, args=(lock,))
t2 = threading.Thread(target= taskofThread, args=(lock,))
t1.start()
t2.start()
t1.join()
t2.join()
if __name__ == "__main__":
for i in range(5):
main()
print("x = {1} after Iteration {0}".format(i,x))
De la sortie ci-dessus, il est clair que cProfile imprime toutes les 3577 fonctions appelées, avec le
temps passé dans chacune et le nombre de fois qu'elles ont été appelées. Voici les colonnes que
nous avons reçues en sortie -
ncalls - C'est le nombre d'appels effectués.
tottime - C'est le temps total passé dans la fonction donnée.
percall - Il fait référence au quotient du temps de temps divisé par ncalls.
cumtime- C'est le temps cumulé passé dans cette fonction et dans toutes les sous-
fonctions. Il est même précis pour les fonctions récursives.
percall - C'est le quotient cumtime divisé par les appels primitifs.
filename:lineno(function) - Il fournit essentiellement les données respectives de chaque
fonction.
Classe d'exécuteur
Executorest une classe abstraite du concurrent.futuresModule Python. Il ne peut pas être
utilisé directement et nous devons utiliser l'une des sous-classes concrètes suivantes -
ThreadPoolExecutor
ProcessPoolExecutor
ThreadPoolExecutor - Une sous-classe concrète
C'est l'une des sous-classes concrètes de la classe Executor. La sous-classe utilise le multi-
threading et nous obtenons un pool de threads pour soumettre les tâches. Ce pool affecte des
tâches aux threads disponibles et planifie leur exécution.
Exemple
from concurrent.futures import ThreadPoolExecutor
from time import sleep
def task(message):
sleep(2)
return message
def main():
executor = ThreadPoolExecutor(5)
future = executor.submit(task, ("Completed"))
print(future.done())
sleep(2)
print(future.done())
print(future.result())
if __name__ == '__main__':
main()
Production
False
True
Completed
Dans l'exemple ci-dessus, un ThreadPoolExecutora été construit avec 5 fils. Ensuite, une tâche,
qui attendra 2 secondes avant de donner le message, est soumise à l'exécuteur du pool de
threads. Comme le montre la sortie, la tâche ne se termine pas avant 2 secondes, donc le
premier appel àdone()retournera False. Au bout de 2 secondes, la tâche est terminée et nous
obtenons le résultat du futur en appelant leresult() méthode là-dessus.
Exemple
L'exemple suivant est emprunté à la documentation Python. Dans cet exemple, tout d'abord
leconcurrent.futuresle module doit être importé. Puis une fonction nomméeload_url()est créé
qui chargera l'url demandée. La fonction crée alorsThreadPoolExecutoravec les 5 fils dans la
piscine. leThreadPoolExecutora été utilisé comme gestionnaire de contexte. Nous pouvons
obtenir le résultat du futur en appelant leresult() méthode là-dessus.
import concurrent.futures
import urllib.request
URLS = ['http://www.foxnews.com/',
'http://www.cnn.com/',
'http://europe.wsj.com/',
'http://www.bbc.co.uk/',
'http://some-made-up-domain.com/']
Production
Voici la sortie du script Python ci-dessus -
Exemple
Dans cet exemple ci-dessous, la fonction de carte est utilisée pour appliquer le square() fonction
à chaque valeur du tableau de valeurs.
Production
Le script Python ci-dessus génère la sortie suivante -
4
9
16
25
Classe d'exécuteur
Executor est une classe abstraite du concurrent.futuresModule Python. Il ne peut pas être
utilisé directement et nous devons utiliser l'une des sous-classes concrètes suivantes -
ThreadPoolExecutor
ProcessPoolExecutor
Exemple
Nous allons maintenant considérer le même exemple que nous avons utilisé lors de la création
du pool de threads, la seule différence étant que nous allons maintenant
utiliser ProcessPoolExecutor au lieu de ThreadPoolExecutor .
def main():
executor = ProcessPoolExecutor(5)
future = executor.submit(task, ("Completed"))
print(future.done())
sleep(2)
print(future.done())
print(future.result())
if __name__ == '__main__':
main()
Production
False
False
Completed
Dans l'exemple ci-dessus, un processusPoolExecutora été construit avec 5 fils. Ensuite, une
tâche, qui attendra 2 secondes avant de donner le message, est soumise à l'exécuteur du pool
de processus. Comme le montre la sortie, la tâche ne se termine pas avant 2 secondes, donc le
premier appel àdone()retournera False. Au bout de 2 secondes, la tâche est terminée et nous
obtenons le résultat du futur en appelant leresult() méthode là-dessus.
Instanciation de ProcessPoolExecutor - Gestionnaire de
contexte
Une autre façon d'instancier ProcessPoolExecutor consiste à utiliser le gestionnaire de contexte.
Cela fonctionne de manière similaire à la méthode utilisée dans l'exemple ci-dessus. Le principal
avantage de l'utilisation du gestionnaire de contexte est qu'il a une bonne syntaxe. L'instanciation
peut être effectuée à l'aide du code suivant -
Exemple
Pour une meilleure compréhension, nous prenons le même exemple que celui utilisé lors de la
création d'un pool de threads. Dans cet exemple, nous devons commencer par importer
leconcurrent.futuresmodule. Puis une fonction nomméeload_url()est créé qui chargera l'url
demandée. leProcessPoolExecutorest alors créé avec le nombre de 5 threads dans le pool. Le
processusPoolExecutora été utilisé comme gestionnaire de contexte. Nous pouvons obtenir le
résultat du futur en appelant leresult() méthode là-dessus.
import concurrent.futures
from concurrent.futures import ProcessPoolExecutor
import urllib.request
URLS = ['http://www.foxnews.com/',
'http://www.cnn.com/',
'http://europe.wsj.com/',
'http://www.bbc.co.uk/',
'http://some-made-up-domain.com/']
def main():
with concurrent.futures.ProcessPoolExecutor(max_workers=5) as executor:
future_to_url = {executor.submit(load_url, url, 60): url for url in
URLS}
for future in concurrent.futures.as_completed(future_to_url):
url = future_to_url[future]
try:
data = future.result()
except Exception as exc:
print('%r generated an exception: %s' % (url, exc))
else:
print('%r page is %d bytes' % (url, len(data)))
if __name__ == '__main__':
main()
Production
Le script Python ci-dessus générera la sortie suivante -
Exemple
Nous considérerons le même exemple que nous avons utilisé lors de la création d'un pool de
threads en utilisant le Executor.map()fonction. Dans l'exemple donné ci-dessous, la fonction
map est utilisée pour appliquersquare() fonction à chaque valeur du tableau de valeurs.
Production
Le script Python ci-dessus générera la sortie suivante
4
9
16
25
Exemple
import time
import concurrent.futures
def counting(n):
start = time.time()
while n > 0:
n -= 1
return time.time() - start
def main():
start = time.time()
with concurrent.futures.ProcessPoolExecutor() as executor:
for number, time_taken in zip(value, executor.map(counting, value)):
print('Start: {} Time taken: {}'.format(number, time_taken))
print('Total time taken: {}'.format(time.time() - start))
if __name__ == '__main__':
main()
Production
Start: 8000000 Time taken: 1.5509998798370361
Start: 7000000 Time taken: 1.3259999752044678
Total time taken: 2.0840001106262207
def counting(n):
start = time.time()
while n > 0:
n -= 1
return time.time() - start
def main():
start = time.time()
with concurrent.futures.ThreadPoolExecutor() as executor:
for number, time_taken in zip(value, executor.map(counting, value)):
print('Start: {} Time taken: {}'.format(number, time_taken))
print('Total time taken: {}'.format(time.time() - start))
if __name__ == '__main__':
main()
Production
Start: 8000000 Time taken: 3.8420000076293945
Start: 7000000 Time taken: 3.6010000705718994
Total time taken: 3.8480000495910645
À partir des sorties des deux programmes ci-dessus, nous pouvons voir la différence de temps
d'exécution lors de l'utilisation ProcessPoolExecutor et ThreadPoolExecutor.
Multitraitement
Il s'agit de l'utilisation de deux unités de processeur ou plus dans un même système informatique.
C'est la meilleure approche pour tirer le plein potentiel de notre matériel en utilisant le nombre
total de cœurs de processeur disponibles dans notre système informatique.
Multithreading
C'est la capacité d'un processeur à gérer l'utilisation du système d'exploitation en exécutant
plusieurs threads simultanément. L'idée principale du multithreading est de réaliser le
parallélisme en divisant un processus en plusieurs threads.
Le tableau suivant montre certaines des différences importantes entre eux -
Multitraitement Multiprogrammation
Moins de temps pour traiter les travaux. Plus de temps pour traiter les travaux.
Il facilite une utilisation beaucoup plus efficace des appareils du Moins efficace que le multitraitement.
système informatique.
Exemple
L'exemple de script Python suivant vous aidera à comprendre comment créer un nouveau
processus enfant et obtenir les PID des processus enfants et parents -
import os
def child():
n = os.fork()
if n > 0:
print("PID of Parent process is : ", os.getpid())
else:
print("PID of Child process is : ", os.getpid())
child()
Production
PID of Parent process is : 25989
PID of Child process is : 25990
Exemple
L'exemple suivant de script Python aide à générer trois processus
import multiprocessing
def spawn_process(i):
print ('This is process: %s' %i)
return
if __name__ == '__main__':
Process_jobs = []
for i in range(3):
p = multiprocessing.Process(target = spawn_process, args = (i,))
Process_jobs.append(p)
p.start()
p.join()
Production
This is process: 0
This is process: 1
This is process: 2
Exemple
Ici, nous utilisons le même exemple que celui utilisé dans les threads du démon. La seule
différence est le changement de module demultithreading à multiprocessinget définissant
l'indicateur démoniaque sur true. Cependant, il y aurait un changement de sortie comme indiqué
ci-dessous -
import multiprocessing
import time
def nondaemonProcess():
print("starting my Process")
time.sleep(8)
print("ending my Process")
def daemonProcess():
while True:
print("Hello")
time.sleep(2)
if __name__ == '__main__':
nondaemonProcess = multiprocessing.Process(target = nondaemonProcess)
daemonProcess = multiprocessing.Process(target = daemonProcess)
daemonProcess.daemon = True
nondaemonProcess.daemon = False
daemonProcess.start()
nondaemonProcess.start()
Production
starting my Process
ending my Process
La sortie est différente par rapport à celle générée par les threads de démon, car le processus en
mode sans démon a une sortie. Par conséquent, le processus démoniaque se termine
automatiquement après la fin des programmes principaux pour éviter la persistance des
processus en cours d'exécution.
Exemple
import multiprocessing
import time
def Child_process():
print ('Starting function')
time.sleep(5)
print ('Finished function')
P = multiprocessing.Process(target = Child_process)
P.start()
print("My Process has terminated, terminating main thread")
print("Terminating Child Process")
P.terminate()
print("Child Process successfully terminated")
Production
My Process has terminated, terminating main thread
Terminating Child Process
Child Process successfully terminated
La sortie montre que le programme se termine avant l'exécution du processus enfant qui a été
créé à l'aide de la fonction Child_process (). Cela implique que le processus enfant s'est terminé
avec succès.
import multiprocessing
print(multiprocessing.current_process().pid)
Exemple
L'exemple suivant de script Python aide à trouver le PID du processus principal ainsi que le PID
du processus enfant -
import multiprocessing
import time
def Child_process():
print("PID of Child Process is:
{}".format(multiprocessing.current_process().pid))
print("PID of Main process is:
{}".format(multiprocessing.current_process().pid))
P = multiprocessing.Process(target=Child_process)
P.start()
P.join()
Production
PID of Main process is: 9401
PID of Child Process is: 9402
Exemple
import multiprocessing
class MyProcess(multiprocessing.Process):
def run(self):
print ('called run method in process: %s' %self.name)
return
if __name__ == '__main__':
jobs = []
for i in range(5):
P = MyProcess()
jobs.append(P)
P.start()
P.join()
Production
called run method in process: MyProcess-1
called run method in process: MyProcess-2
called run method in process: MyProcess-3
called run method in process: MyProcess-4
called run method in process: MyProcess-5
Module multiprocesseur Python - Classe
de pool
Si nous parlons de parallèle simple processingtâches dans nos applications Python, puis le
module multiprocesseur nous fournit la classe Pool. Les méthodes suivantes dePool la classe
peut être utilisée pour lancer un certain nombre de processus enfants dans notre programme
principal
apply () méthode
Cette méthode est similaire à la.submit()méthode de .ThreadPoolExecutor.Il bloque jusqu'à ce
que le résultat soit prêt.
méthode apply_async ()
Lorsque nous avons besoin d'une exécution parallèle de nos tâches, nous devons utiliser
leapply_async()méthode pour soumettre des tâches au pool. Il s'agit d'une opération
asynchrone qui ne verrouille pas le thread principal tant que tous les processus enfants ne sont
pas exécutés.
map () méthode
Tout comme le apply()méthode, il bloque également jusqu'à ce que le résultat soit prêt. C'est
l'équivalent du intégrémap() fonction qui divise les données itérables en un certain nombre de
blocs et les soumet au pool de processus en tant que tâches distinctes.
Exemple
L'exemple suivant vous aidera à implémenter un pool de processus pour effectuer une exécution
parallèle. Un simple calcul du carré du nombre a été effectué en appliquant lasquare() fonction à
travers le multiprocessing.Poolméthode. ensuitepool.map() a été utilisé pour soumettre le 5,
car l'entrée est une liste d'entiers de 0 à 4. Le résultat serait stocké dans p_outputs et il est
imprimé.
def square(n):
result = n*n
return result
if __name__ == '__main__':
inputs = list(range(5))
p = multiprocessing.Pool(processes = 4)
p_outputs = pool.map(function_square, inputs)
p.close()
p.join()
print ('Pool :', p_outputs)
Production
Pool : [0, 1, 4, 9, 16]
Processus Intercommunication
L'intercommunication de processus signifie l'échange de données entre les processus. Il est
nécessaire d'échanger les données entre les processus pour le développement d'applications
parallèles. Le diagramme suivant montre les différents mécanismes de communication pour la
synchronisation entre plusieurs sous-processus -
Files d'attente
Les files d'attente peuvent être utilisées avec des programmes multi-processus. La classe Queue
demultiprocessing module est similaire au Queue.Queueclasse. Par conséquent, la même API
peut être utilisée.Multiprocessing.Queue nous fournit un mécanisme de communication FIFO
(premier entré premier sorti) sûr pour les threads et les processus.
Exemple
Voici un exemple simple tiré de la documentation officielle de python sur le multitraitement pour
comprendre le concept de classe de file d'attente du multitraitement.
Production
[42, None, 'hello']
Tuyaux
C'est une structure de données, qui est utilisée pour communiquer entre les processus dans les
programmes multi-processus. La fonction Pipe () renvoie une paire d'objets de connexion
connectés par un tuyau qui par défaut est duplex (bidirectionnel). Cela fonctionne de la manière
suivante -
Il renvoie une paire d'objets de connexion qui représentent les deux extrémités du tuyau.
Chaque objet a deux méthodes - send() et recv(), pour communiquer entre les
processus.
Exemple
Voici un exemple simple tiré de la documentation officielle de python sur le multitraitement pour
comprendre le concept de Pipe() fonction du multitraitement.
def f(conn):
conn.send([42, None, 'hello'])
conn.close()
if __name__ == '__main__':
parent_conn, child_conn = Pipe()
p = Process(target = f, args = (child_conn,))
p.start()
print (parent_conn.recv())
p.join()
Production
[42, None, 'hello']
Directeur
Manager est une classe de module multitraitement qui fournit un moyen de coordonner les
informations partagées entre tous ses utilisateurs. Un objet gestionnaire contrôle un processus
serveur, qui gère les objets partagés et permet à d'autres processus de les manipuler. En
d'autres termes, les gestionnaires fournissent un moyen de créer des données qui peuvent être
partagées entre différents processus. Voici les différentes propriétés de l'objet gestionnaire -
La propriété principale du manager est de contrôler un processus serveur, qui gère les
objets partagés.
Une autre propriété importante est de mettre à jour tous les objets partagés lorsqu'un
processus le modifie.
Exemple
Voici un exemple qui utilise l'objet gestionnaire pour créer un enregistrement de liste dans le
processus serveur, puis ajouter un nouvel enregistrement dans cette liste.
import multiprocessing
def print_records(records):
for record in records:
print("Name: {0}\nScore: {1}\n".format(record[0], record[1]))
if __name__ == '__main__':
with multiprocessing.Manager() as manager:
records = manager.list([('Computers', 1), ('Histoty', 5),
('Hindi',9)])
new_record = ('English', 3)
Production
A New record is added
Name: Computers
Score: 1
Name: Histoty
Score: 5
Name: Hindi
Score: 9
Name: English
Score: 3
Exemple
L'exemple de script Python suivant nous aide à utiliser les espaces de noms pour partager des
données entre le processus principal et le processus enfant -
import multiprocessing
def Mng_NaSp(using_ns):
using_ns.x +=5
using_ns.y *= 10
if __name__ == '__main__':
manager = multiprocessing.Manager()
using_ns = manager.Namespace()
using_ns.x = 1
using_ns.y = 1
Production
before Namespace(x = 1, y = 1)
after Namespace(x = 6, y = 10)
Ctypes-Array et valeur
Le module de multitraitement fournit des objets Array et Value pour stocker les données dans
une carte de mémoire partagée. Array est un tableau ctypes alloué à partir de la mémoire
partagée et Value est un objet ctypes alloué à partir de la mémoire partagée.
Pour être avec, importez Process, Value, Array à partir du multiprocessing.
Exemple
Le script Python suivant est un exemple tiré de la documentation python pour utiliser Ctypes
Array et Value pour partager certaines données entre les processus.
if __name__ == '__main__':
num = Value('d', 0.0)
arr = Array('i', range(10))
Production
3.1415927
[0, -1, -2, -3, -4, -5, -6, -7, -8, -9]
Dans le réseau de processus PyCSP ci-dessus, il existe deux processus - Process1 et Process
2. Ces processus communiquent en passant des messages via deux canaux - canal 1 et canal 2.
Installation de PyCSP
Avec l'aide de la commande suivante, nous pouvons installer la bibliothèque Python PyCSP -
Exemple
Le script Python suivant est un exemple simple pour exécuter deux processus en parallèle l'un de
l'autre. Cela se fait à l'aide de la bibliothèque Python PyCSP -
Dans le script ci-dessus, deux fonctions à savoir P1 et P2 ont été créés puis décorés
avec @process pour les convertir en processus.
Production
P2 exiting
P1 exiting
Terminating
Programmation événementielle
La programmation événementielle se concentre sur les événements. Finalement, le déroulement
du programme dépend des événements. Jusqu'à présent, nous avions affaire à un modèle
d'exécution séquentielle ou parallèle, mais le modèle ayant le concept de programmation
événementielle est appelé modèle asynchrone. La programmation événementielle dépend d'une
boucle d'événements qui écoute toujours les nouveaux événements entrants. Le fonctionnement
de la programmation événementielle dépend des événements. Une fois qu'un événement boucle,
les événements décident de ce qu'il faut exécuter et dans quel ordre. L'organigramme suivant
vous aidera à comprendre comment cela fonctionne -
La boucle d'événements
Event-loop est une fonctionnalité permettant de gérer tous les événements dans un code de
calcul. Il agit en boucle pendant l'exécution du programme entier et garde une trace de l'arrivée et
de l'exécution des événements. Le module Asyncio autorise une seule boucle d'événements par
processus. Voici quelques méthodes fournies par le module Asyncio pour gérer une boucle
d'événements -
loop = get_event_loop() - Cette méthode fournira la boucle d'événements pour le
contexte actuel.
loop.call_later(time_delay,callback,argument) - Cette méthode organise le rappel qui
doit être appelé après les secondes time_delay données.
loop.call_soon(callback,argument)- Cette méthode organise un rappel qui doit être
appelé dès que possible. Le rappel est appelé après le retour de call_soon () et lorsque le
contrôle revient à la boucle d'événements.
loop.time() - Cette méthode permet de renvoyer l'heure actuelle en fonction de l'horloge
interne de la boucle d'événements.
asyncio.set_event_loop() - Cette méthode définira la boucle d'événements du contexte
actuel sur la boucle.
asyncio.new_event_loop() - Cette méthode créera et retournera un nouvel objet de
boucle d'événement.
loop.run_forever() - Cette méthode fonctionnera jusqu'à ce que la méthode stop () soit
appelée.
Exemple
L'exemple suivant de boucle d'événement aide à l'impression hello worlden utilisant la méthode
get_event_loop (). Cet exemple est tiré de la documentation officielle de Python.
import asyncio
def hello_world(loop):
print('Hello World')
loop.stop()
loop = asyncio.get_event_loop()
loop.call_soon(hello_world, loop)
loop.run_forever()
loop.close()
Production
Hello World
Futures
Ceci est compatible avec la classe concurrent.futures.Future qui représente un calcul qui n'a pas
été effectué. Il existe les différences suivantes entre asyncio.futures.Future et
concurrent.futures.Future -
Les méthodes result () et exception () ne prennent pas d'argument timeout et lèvent une
exception lorsque le futur n'est pas encore terminé.
Les rappels enregistrés avec add_done_callback () sont toujours appelés via la fonction
call_soon () de la boucle d'événements.
La classe asyncio.futures.Future n'est pas compatible avec les fonctions wait () et
as_completed () du package concurrent.futures.
Exemple
Voici un exemple qui vous aidera à comprendre comment utiliser la classe asyncio.futures.future.
import asyncio
loop = asyncio.get_event_loop()
future = asyncio.Future()
asyncio.ensure_future(Myoperation(future))
try:
loop.run_until_complete(future)
print(future.result())
finally:
loop.close()
Production
Future Completed
Coroutines
Le concept de coroutines dans Asyncio est similaire au concept d'objet Thread standard sous le
module de threading. C'est la généralisation du concept de sous-programme. Une coroutine peut
être suspendue pendant l'exécution pour qu'elle attende le traitement externe et retourne à partir
du point où elle s'était arrêtée lorsque le traitement externe a été effectué. Les deux méthodes
suivantes nous aident à implémenter des coroutines -
import asyncio
loop = asyncio.get_event_loop()
try:
loop.run_until_complete(Myoperation())
finally:
loop.close()
Production
First Coroutine
@ asyncio.coroutine décorateur
Une autre méthode d'implémentation des coroutines consiste à utiliser des générateurs avec le
décorateur @ asyncio.coroutine. Voici un script Python pour le même -
import asyncio
@asyncio.coroutine
def Myoperation():
print("First Coroutine")
loop = asyncio.get_event_loop()
try:
loop.run_until_complete(Myoperation())
finally:
loop.close()
Production
First Coroutine
Tâches
Cette sous-classe du module Asyncio est responsable de l'exécution des coroutines dans une
boucle d'événements de manière parallèle. Le script Python suivant est un exemple de traitement
de certaines tâches en parallèle.
import asyncio
import time
async def Task_ex(n):
time.sleep(1)
print("Processing {}".format(n))
async def Generator_task():
for i in range(10):
asyncio.ensure_future(Task_ex(i))
int("Tasks Completed")
asyncio.sleep(2)
loop = asyncio.get_event_loop()
loop.run_until_complete(Generator_task())
loop.close()
Production
Tasks Completed
Processing 0
Processing 1
Processing 2
Processing 3
Processing 4
Processing 5
Processing 6
Processing 7
Processing 8
Processing 9
Les transports
Le module Asyncio fournit des classes de transport pour implémenter différents types de
communication. Ces classes ne sont pas thread-safe et toujours associées à une instance de
protocole après l'établissement du canal de communication.
Voici différents types de transports hérités de BaseTransport -
ReadTransport - Il s'agit d'une interface pour les transports en lecture seule.
WriteTransport - Il s'agit d'une interface pour les transports en écriture seule.
DatagramTransport - Ceci est une interface pour l'envoi des données.
BaseSubprocessTransport - Similaire à la classe BaseTransport.
Les éléments suivants sont cinq méthodes distinctes de la classe BaseTransport qui sont par la
suite transitoires sur les quatre types de transport -
close() - Il ferme le transport.
is_closing() - Cette méthode retournera true si le transport se ferme ou est déjà fermé.
get_extra_info(name, default = none) - Cela nous donnera des informations
supplémentaires sur le transport.
get_protocol() - Cette méthode retournera le protocole actuel.
Protocoles
Le module Asyncio fournit des classes de base que vous pouvez sous-classer pour implémenter
vos protocoles réseau. Ces classes sont utilisées en conjonction avec les transports; le protocole
analyse les données entrantes et demande l'écriture des données sortantes, tandis que le
transport est responsable des E / S et de la mise en mémoire tampon. Voici trois classes de
protocole -
Protocol - Il s'agit de la classe de base pour l'implémentation de protocoles de streaming
à utiliser avec les transports TCP et SSL.
DatagramProtocol - Il s'agit de la classe de base pour l'implémentation des protocoles
de datagramme à utiliser avec les transports UDP.
SubprocessProtocol - Il s'agit de la classe de base pour l'implémentation de protocoles
communiquant avec des processus enfants via un ensemble de canaux unidirectionnels.
Programmation réactive
La programmation réactive est un paradigme de programmation qui traite des flux de données et
de la propagation du changement. Cela signifie que lorsqu'un flux de données est émis par un
composant, le changement sera propagé à d'autres composants par la bibliothèque de
programmation réactive. La propagation du changement continuera jusqu'à ce qu'il atteigne le
récepteur final. La différence entre la programmation événementielle et la programmation
réactive est que la programmation événementielle tourne autour d'événements et la
programmation réactive tourne autour des données.
Classe observable
Cette classe est la source du flux de données ou des événements et elle emballe les données
entrantes afin que les données puissent être transmises d'un thread à un autre. Il ne donnera pas
de données tant qu'un observateur ne s'y sera pas abonné.
Classe d'observateur
Cette classe consomme le flux de données émis par observable. Il peut y avoir plusieurs
observateurs avec observable et chaque observateur recevra chaque élément de données émis.
L'observateur peut recevoir trois types d'événements en souscrivant à observable -
on_next() event - Cela implique qu'il y a un élément dans le flux de données.
on_completed() event - Cela implique la fin de l'émission et il n'y a plus d'articles à venir.
on_error() event - Cela implique également la fin de l'émission mais dans le cas où une
erreur est lancée par observable.
Exemple
Voici un script Python, qui utilise RxPY module et ses classes Observable et Observe
forprogrammation réactive. Il existe essentiellement deux classes -
get_strings() - pour obtenir les chaînes de l'observateur.
PrintObserver()- pour imprimer les chaînes de l'observateur. Il utilise les trois
événements de la classe observateur. Il utilise également la classe subscribe ().
Production
Received Ram
Received Mohan
Received Shyam
Finished
Exemple
L'exemple suivant utilise the PyFunctional module et son seqclass qui agit comme l'objet de flux
avec lequel nous pouvons itérer et manipuler. Dans ce programme, il mappe la séquence en
utilisant la fonction lamda qui double chaque valeur, puis filtre la valeur où x est supérieur à 4 et
finalement réduit la séquence en une somme de toutes les valeurs restantes.
Production
Result: 6