This is an old revision of the document!
Dans les laboratoires précédents, le concept process était présenté, il s'agissait de la principale unité d'allocation de ressources pour les utilisateurs. Dans ce laboratoire, le concept de thread (ou thread ) est présenté, il s'agit de l'unité de planification élémentaire d'un système. En plus des processus, les threads d'exécution constituent un mécanisme par lequel un ordinateur peut exécuter plusieurs tâches simultanément.
Un thread d'exécution existe dans un processus et c'est une unité plus fine qu'elle ne l'est. Lorsqu'un processus est créé, il ne contient qu'un seul thread qui exécute le programme séquentiel. Ce fil peut à son tour créer d'autres threads; ces threads exécuteront des portions du binaire associé au processus en cours, éventuellement identiques au thread d'origine (qui les a créées).
/ dev / sda1
). (E) IP
)Les processus sont utilisés par le responsable de sécurité pour regrouper et allouer des ressources, et les threads pour planifier l'exécution du code qui accède à ces ressources (partagées).
Étant donné que tous les threads d'un processus utilisent l'espace d'adressage du processus auquel ils appartiennent, leur utilisation présente de nombreux avantages:
Les threads d'exécution peuvent être utiles dans de nombreuses situations, par exemple pour améliorer le temps de réponse des applications à interface graphique, où un traitement intensif du processeur est généralement effectué dans un thread différent de celui affichant l'interface. .
Ils simplifient également la structure d'un programme et permettent d'utiliser moins de ressources (car différentes formes d'IPC ne sont pas nécessaires pour communiquer).
Du point de vue de la mise en œuvre, il existe 3 catégories de threads:
En ce qui concerne les threads, POSIX ne spécifie pas s'ils doivent être implémentés dans l'espace utilisateur ou dans l'espace noyau. Linux les implémente dans l'espace noyau, mais ne différencie pas les threads de processus, sauf que les threads partagent l'espace d'adressage (les threads d'exécution et les processus constituent un cas particulier de “tâche”). Pour utiliser les threads en Python, nous devons inclure le module threading.
Le module threading
expose la classe Thread
. De cette maniere, un fil d'execution est créé lorsqu'on initialise la classe:
import threading threading.Thread(group=None, target=None, name=None, args=(), kwargs={}, *, daemon=None)
None
Thread-N
, ou N est un numérotarget
;
Le nouveau fil créé peut etre lancé en exécution en appelant la fonction start(). Il va exécuter le code spécifié par la fonction target
a laquelle on va passer les arguments de args
ou kwargs
.
Pour déterminer le fil d'exécution courant, on peut utiliser la fonction current_thread:
import threading threading.current_thread()
Les threads d'exécution sont attendus à l'aide de la fonction pthread_join:
t = threading.Thread() t.join(timeout=None)
Une fois qu'un thread a exécuté join sur un autre, celui-ci sera bloqué jusqu'a ce que le thread sur lequel on a fait join va terminer son exécution. Si le thread sur lequel on a fait join lancera une excéption du type excepthook sera lancée dans le thread qui a fait join.
join
dans le programme principal pour les threads qu'elle génere.
Un thread de type daemon a comme but de processer certaines opérations sans avoir un impact sur le fil d'exécution principal.
La propriété principale de ces threads est que le programme principal va finir son exécution s'il ne contient que des threads de ce type.
join
sur un thread de type daemon.
Un thread finit son exécution automatiquement, a la fin du code du fil d'exécution.
Pour changer des informations entre le programme principal et d'autres threads, on va utiliser le module queue. A l'aide de ce module, on peut implémenter une queue ou on va placer les messages provenus de la part des fils d'exécution.
Le module supporte la création de 6 types différentes de queues:
queue
, vous pouvez lire la documentation.
Pour créer un queue, on doit instancier l'une des classes du module queue
:
import queue fifo_queue = queue.Queue() lifo_queue = queue.LifoQueue() priority_queue = queue.PriorityQueue()
Les fonctions put et put_nowait ajoutent des éléments dans la queue:
q.put (item, block=True, timeout=None) q.put_nowait (item)
Les fonctions get et get_nowait sortent des éléments de la queue:
q.get (block=True, timeout=None) q.get_nowait ()
Si la queue est vide a l'appel de l'une de ces 2 fonctions, on va lancer une exception de type Empty
On va créer un programme qui génere un thread qui recoit des valeurs du programme principal et qu'y répond:
import queue import threading def worker (): while True: item = q1.get() if item == 'Hello': print (item) q2.put ('SDE') elif item == 'Good': print (item) q2.put ('bye') else: break q1 = queue.Queue() q2 = queue.Queue() t = threading.Thread (target=worker) t.start () q1.put ('Hello') print (q2.get ()) q1.put ('Good') print (q2.get ()) q1.put ('exit') t.join()
Parfois, il est utile qu'une variable soit spécifique à un thread d'exécution (invisible pour les autres threads). Linux permet aux paires d'être stockées (clé, valeur) dans une zone spécialement désignée sur la pile de chaque thread du processus en cours. La clé a le même rôle que le nom d'une variable: elle désigne l'emplacement de la mémoire où se trouve la valeur.
Chaque thread aura sa propre copie d'une “variable” correspondant à une clé k
, qu'il pourra modifier sans que cela soit remarqué par les autres fils ou nécessitant une synchronisation. Par conséquent, TSD est parfois utilisé pour optimiser les opérations nécessitant beaucoup de synchronisation entre les threads: chaque thread calcule les informations spécifiques et une seule étape de synchronisation est nécessaire à la fin pour fusionner les résultats de tous les threads.
Les clés sont pthread_key_t et les valeurs qui leur sont associées sont du type générique void * (pointeurs vers l'emplacement de la pile où la variable est stockée). Nous décrivons maintenant les opérations disponibles avec les variables dans TSD:
Une variable est créée en utilisant la classe https://docs.python.org/3/library/threading.html#threading.local"local du module threading
.
import threading local_data = threading.local() local_data.my_var = 'SDE'
Créez un programme qui recoit 3 parametres de la ligne de commande et qui lance en exécution 3 threads. Chaque thread recevra comme parametre du programme principal l'un des 3 numéros et qui affiche sur l'écran pair
our impair
, en fonction de la parité du numéro.
Dans le fichier tu directoire 3-numbers, démarrez un fils d'exécution qui affiche les numéros de 0 a 5 à l'intervalle d'une seconde.
join
.
Observez le moment ou le processus finit son exécution. Modifiez le programme principal pour que le thread finisse don exécution immédiatement apres avoir affiché les 2 lignes
main 1 main 2
Dans le fichier du directoire 4-alive, lancez en exécution un thread pour chaque fonction worker définie et attendes les threads en utilisant la fonction join (hint: is_alive.
Créez 2 fils d'exécution, l'un qui va générer les numéros primes de 0 a 50 et l'autre qui va générer les numéros carrés parfaits de 0 a 50. Le programme principal va afficher les numéros générées par les 2 threads.
Créez un thread qui va recevoir une commande bash du clavier et qui va l'exécuter.
Utilisez 3 threads pour trouver le maximum d'une liste. Chaque thread recoit une partie de la liste par une queue et met le maximum dans une autre queue. (hint: queue.join)