Differences

This shows you the differences between two versions of the page.

Link to this comparison view

sde:laboratoare:08 [2020/04/07 22:23]
diana.ghindaoanu [Création des discussions]
— (current)
Line 1: Line 1:
-====== TP 8 - Fils d'​Execution sous Linux====== 
-===== Documents d'aide ===== 
- 
-  * [[http://​elf.cs.pub.ro/​so/​res/​laboratoare/​lab08-slides.pdf | lab08-slides.pdf]] 
-  * [[http://​elf.cs.pub.ro/​so/​res/​laboratoare/​lab08-refcard.pdf | lab08-refcard.pdf]] 
- 
-==== Agréable à lire ==== 
- 
-  * TLPI - Chapitre 29, Fil: Introduction 
-  * TLPI - Chapitre 30, Threads: Synchronisation des threads 
-  * TLPI - Chapitre 31, Filetage: Sécurité des filets et stockage par fil 
-===== Présentation théorique ===== 
-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). 
-==== Différences entre les threads d'​exécution et les processus ==== 
- 
-  * Les processus ne partagent pas de ressources entre eux (à moins que le programmeur utilise un mécanisme spécial pour cela, la mémoire partagée par exemple), tandis que les threads partagent la majorité des ressources d'un processus par défaut. Changer une telle ressource depuis un thread est instantanément visible depuis les autres threads: 
-    * segments de mémoire tels que '​.heap',​ '​.data'​ et '​.bss'​ '(donc les variables qui y sont stockées) 
-    * descripteurs de fichier (la fermeture d'un fichier est immédiatement visible pour tous les threads) quel que soit le type de fichier: 
-      * prises; 
-      * fichiers normaux 
-      * Nommé tuyau 
-      * Les fichiers représentant les périphériques matériels (par exemple, ''​ / dev / sda1 ''​). 
-  * Chaque thread a son propre contexte d'​exécution,​ composé de: 
-    * pile 
-    * ensemble de registres (donc un programme - registre ''​ (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). 
-==== Avantages des threads ==== 
- 
-É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: 
-  * Créer / détruire un fil prend moins de temps que créer / détruire un processus 
-  * La longueur du contexte de commutation entre les threads du même processus est très petite, car il n'est pas nécessaire de "​changer"​ l'​espace d'​adressage (pour plus d'​informations,​ recherchez "TLB flush"​) 
-  * la communication entre les threads a une surcharge moins importante (réalisée en modifiant certaines zones de mémoire dans l'​espace d'​adressage partagé) 
- 
-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). 
-==== Types de fils d'​execution ==== 
- 
-Du point de vue de la mise en œuvre, il existe 3 catégories de threads: 
-  * Threads au niveau du noyau (KLT) 
-  * Threads de niveau utilisateur (ULT) 
-  * Fils hybrides 
- 
-<spoiler Détails des types de fil> 
- 
-** Threads au niveau du noyau ** 
- 
-La gestion des threads et la planification se font dans le noyau; les programmes créent / détruisent les fils d'​exécution par le biais d'​appels système. Le noyau conserve les informations de contexte pour les processus et les threads de processus, et la planification de l'​exécution est basée sur les threads. 
- 
-__Avantaje__:​ 
-  * Si nous avons plusieurs processeurs,​ nous pouvons exécuter simultanément plusieurs threads du même processus. 
-  * bloquer un thread ne signifie pas bloquer l'​ensemble du processus; 
-  * nous pouvons écrire du code noyau basé sur des threads. 
- 
-__Desavantaje__:​ 
-  * la commutation de contexte est effectuée par le noyau (avec une vitesse de commutation inférieure):​ 
-    * passe d'un thread de noyau 
-    * Le noyau retourne le contrôle d'un autre thread. 
- 
-** Threads de niveau utilisateur ** 
- 
-Le noyau n'est pas au courant de l'​existence des threads et leur gestion est assurée par le processus dans lequel ils existent (la mise en œuvre de la gestion des threads est généralement effectuée dans des bibliothèques). Changer le contexte ne nécessite pas d'​intervention du noyau et l'​algorithme de planification dépend de l'​application. 
- 
-__Avantaje__:​ 
-  * Le changement de contexte n'​implique pas le noyau ⇒ changement rapide 
-  * la planification peut être choisie par l'​application;​ l'​application peut utiliser cette planification d'​amélioration des performances 
-  * Les threads d'​exécution peuvent s'​exécuter sur n'​importe quel SO, y compris les SO qui ne prennent pas en charge les threads du noyau (ils n'ont besoin que de la bibliothèque qui implémente les threads générés par l'​utilisateur). 
- 
-__Desavantaje__:​ 
-  * Le noyau ne connaît pas les threads ⇒ Si un thread exécute un appel bloquant, tous les threads d'​exécution planifiés par l'​application seront bloqués. Cela peut constituer un obstacle, car la plupart des appels système sont bloquants. Une solution consiste à utiliser des variantes non bloquantes pour les appels système. 
-  * Vous ne pouvez pas tirer le meilleur parti des ressources matérielles:​ le noyau planifie les threads qu'il connaît, un par processeur. Le noyau n'est pas conscient de l'​existence du niveau utilisateur ⇒ les threads ne verront qu'un seul thread ⇒ il planifiera le processus pour un maximum d'un processeur, même si l'​application aurait plusieurs threads planaires en même temps. 
- 
-** Fils de construction hybrides ** 
- 
-Ces threads tentent de combiner les avantages des threads de niveau utilisateur avec ceux des threads de niveau noyau. Une solution consiste à utiliser des câbles au niveau du noyau pour multiplexer des threads au niveau utilisateur. Les KLT sont les unités de base pouvant être réparties sur les processeurs. En règle générale, la création de threads se fait dans l'​espace utilisateur,​ ce qui représente presque toute la planification et la synchronisation. Le noyau ne connaît que les KLT sur lesquels les ULT sont multiplexés et il ne les planifie que. Le planificateur peut éventuellement changer le numéro KLT attribué à un processus. 
- 
-</​spoiler>​ 
-===== Le module threading ===== 
- 
-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 [[https://​docs.python.org/​3/​library/​threading.html|threading]]. 
-==== Création des fils d'​execution==== 
- 
-Le module ''​threading''​ expose la classe ''​Thread''​. De cette maniere, un fil d'​execution est créé lorsqu'​on initialise la classe: 
-<code python> 
-import threading 
-threading.Thread(group=None,​ target=None,​ name=None, args=(), kwargs={}, *, daemon=None) 
-</​code>​ 
- 
-   * group - utilisé pour étendre la classe Thread; on recommande de garder la valeur ''​None''​ 
-   * target - la fonction qui sera appelée au moment d'​exécution du thread 
-   * name - nom du thread; s'il n'est pas spécifié, on va générer un nom de la forme ''​Thread-N'',​ ou N est un numéro 
-   * args - la liste des arguments qui sera envoyée a la fonction ''​target'';​ 
-   * kwargs - un dictionnaire qui va sauvegarder les parametres de la fonction target, sous la forme nom-valeur; 
-   * daemon - spécifie si le thread est de type daemon; si la valeur est None, la propriété sera héritée du thread courant 
- 
-Le nouveau fil créé peut etre lancé en exécution en appelant la fonction [[https://​docs.python.org/​3/​library/​threading.html#​threading.Thread.start|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 [[https://​docs.python.org/​3/​library/​threading.html#​threading.current_thread|current_thread]]:​ 
-<code python> 
-import threading 
-threading.current_thread() 
-</​code>​ 
-==== En attente des discussions ==== 
- 
-Les threads d'​exécution sont attendus à l'aide de la fonction [[http://​linux.die.net/​man/​3/​pthread_join|pthread_join]]:​ 
- 
-<code c> 
-int pthread_join(pthread_t th, void **thread_return);​ 
-</​code>​ 
- 
-Le premier paramètre spécifie l'​identificateur de thread attendu, et le second spécifie l'​emplacement de la valeur renvoyée par la fonction enfant (via [[http://​linux.die.net/​man/​3/​pthread_exit|pthread_exit]] ou via -a ''​ retour ''​ de la routine utilisée dans [[http://​linux.die.net/​man/​3/​pthread_create|pthread_create]]). 
- 
-Les threads d'​exécution appartiennent à deux catégories:​ __ // unifiable // __ et __ // détachable // __. 
- 
-<spoiler Détails des fils unificateurs et détachables>​ 
- 
-  * // unifiable //: 
-    * Autoriser l'​unification avec d'​autres exécutables qui s'​appellent [[http://​linux.die.net/​man/​3/​pthread_join|pthread_join]]. 
-    * Les ressources remplies de threads ne sont pas publiées immédiatement après la fin du thread, mais sont conservées jusqu'​à ce qu'un autre thread exécute [[http://​linux.die.net/​man/​3/​pthread_join|pthread_join]] (Process zombie) 
-    * implicitement les fils sont unifiables 
-* Amovible 
-    * un fil est détachable si: 
-      * a été créé détachable. 
-      * Cet attribut a été modifié lors de l'​exécution par [[http://​linux.die.net/​man/​3/​pthread_detach|pthread_detach]]. 
-    * ne peut pas exécuter un [[http://​linux.die.net/​man/​3/​pthread_join|pthread_join]] sur eux 
-    * libérera les ressources dès qu'​elles auront fini (analogie avec ignorer le signal SIGCHLD dans le parent à la fin de l'​exécution du processus enfant) 
- 
-</​spoiler>​ 
-==== Finition des threads ==== 
- 
-Un thread exécute son exécution: 
- 
-  * lors d'un appel de fonction [[http://​linux.die.net/​man/​3/​pthread_exit|pthread_exit]]:​ <code c> 
-void pthread_exit(void *retval); 
-</​code>​ 
-  * automatiquement à la fin du code du thread. 
- 
-Le paramètre "​retval"​ indique au parent un message sur la manière de mettre fin à l'​enfant. [[Http://​linux.die.net/​man/​3/​pthread_join|pthread_join]] prendra en charge cette valeur. 
- 
-Les méthodes comme un fil pour en terminer un autre sont: 
-  * Établir un protocole de fin (par exemple, le thread ** ** ** définit une variable globale que ** ** l'​esclave ** vérifie périodiquement). 
-  * Le mécanisme ** thread annulation ** fourni par libpthread. Cependant, cette méthode n'est pas recommandée car elle est lourde et pose des problèmes de nettoyage très délicats. Pour plus de détails: [[so: labs-2013: ressources: threads thread | Terminaison de thread]] 
-==== Données spécifiques aux threads (TSD) ==== 
- 
-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: 
- 
-=== Création et suppression d'une variable === 
- 
-Une variable est créée en utilisant [[http://​linux.die.net/​man/​3/​pthread_key_create|pthread_key_create]]:​ 
- 
-<code c> 
-int pthread_key_create(pthread_key_t *key, void (*destr_function) (void *)); 
-</​code>​ 
-Le deuxième paramètre est une fonction de nettoyage. Il peut avoir l'une des valeurs suivantes: 
-  * ''​ NULL ''​ et est ignoré 
-* pointeur sur une fonction de nettoyage exécutée à la fin du thread 
- 
-Pour supprimer une variable, appelez [[http://​linux.die.net/​man/​3/​pthread_key_delete|pthread_key_delete]]:​ 
- 
-<code c> 
-int pthread_key_delete(pthread_key_t key); 
-</​code>​ 
- 
-La fonction n'​appelle pas le nettoyage associé à la variable. 
- 
-=== Changer et lire une variable === 
- 
-Après avoir créé la clé, chaque thread peut modifier sa propre copie de la variable associée à l’aide de la fonction [[http://​linux.die.net/​man/​3/​pthread_setspecific|pthread_setspecific]]:​ 
- 
-<code c> 
-int pthread_setspecific(pthread_key_t key, const void *pointer); 
-</​code>​ 
- 
-Pour déterminer la valeur d'une variable de type TSD, utilisez la fonction [[http://​linux.die.net/​man/​3/​pthread_getspecific|pthread_getspecific]]:​ 
- 
-<code c> 
-void* pthread_getspecific(pthread_key_t key); 
-</​code>​ 
-==== Fonctions de nettoyage ==== 
- 
-Les fonctions de nettoyage associées aux TSD peuvent être très utiles pour garantir que les ressources sont libérées lorsqu'​un thread se termine seul ou est terminé par un autre thread. Parfois, il peut être utile de pouvoir spécifier de telles fonctions sans nécessairement créer un TSD. Pour cela, il existe des fonctions de nettoyage. 
- 
-<spoiler Détails des fonctions de nettoyage>​ 
-Une telle fonction de nettoyage est une fonction appelée à la fin d'un thread d'​exécution. Il reçoit un seul paramètre du type 'void *' qui est spécifié lors de l'​enregistrement de la fonction. 
- 
-Une fonction de nettoyage est utilisée pour libérer une ressource uniquement si un thread appelle [[http://​linux.die.net/​man/​3/​pthread_exit|pthread_exit]] ou est terminé par un autre thread à l'aide de [[http://​linux.die.net/​man/​3/​pthread_cancel|pthread_cancel]]. Dans des circonstances normales, lorsqu'​un thread ne se termine pas de manière forcée, la ressource doit être explicitement libérée et la fonction de nettoyage ne doit pas être appelée. 
- 
-Pour enregistrer une telle fonction de nettoyage, utilisez: 
- 
-<code c> 
-void pthread_cleanup_push(void (*routine) (void *), void *arg); 
-</​code>​ 
- 
-Cette fonction reçoit les paramètres sous forme de pointeur sur la fonction en cours d'​enregistrement et sur la valeur de l'​argument à lui transmettre. La fonction '​routine'​ sera appelée avec l'​argument '​arg'​ lorsque le thread sera forcé de sortir. Si plusieurs fonctions de nettoyage sont enregistrées,​ elles seront appelées dans LIFO (les dernières installées seront appelées en premier). 
- 
-Pour chaque appel [[http://​linux.die.net/​man/​3/​pthread_cleanup_push|pthread_cleanup_push]],​ il doit également exister l'​appel approprié [[http://​linux.die.net/​man/​3/​pthread_cleanup_pop_pop|pthread_cleanup_pop]. ] qui a une fonction de nettoyage: 
-<code c> 
-void pthread_cleanup_pop(int execute); 
-</​code>​ 
- 
-Cela deînregistra plus récemment installé la fonction de nettoyage, et si le paramètre ​  ​exécuter ​  est nul et exécuter un testament. 
- 
-** Attention ** appel A [[http://​linux.die.net/​man/​3/​pthread_cleanup_push|pthread_cleanup_push]] doit avoir un appel correspondant [[http://​linux.die.net/​man/​3 / pthread_cleanup_pop | pthread_cleanup_pop]] dans funcţie__ __aceeaşi et __acelaşi niveau imbricare__. 
- 
-Un petit exemple d'​utilisation des fonctions de nettoyage: 
- 
-<code c th_cleanup.c>​ 
- 
-void *alocare_buffer(int size) 
-{ 
- return malloc(size);​ 
-} 
- 
-void dealocare_buffer(void *buffer) 
-{ 
- free(buffer);​ 
-} 
- 
-/* la fonction appelée par un thread */ 
- 
-void functie() 
-{ 
- void *buffer = alocare_buffer(512);​ 
- 
- / * Enregistrement de la fonction de nettoyage * /​ pthread_cleanup_push(dealocare_buffer,​ buffer); 
- 
- / * Ici, le traitement a lieu et pthread_exit peut être appelé 
-         ou le fil peut être terminé par un autre fil * / 
- / * pour enregistrer la fonction de nettoyage et son exécution 
-         (le paramètre donné est différent de zéro) * /  
-        pthread_cleanup_pop(1);​ 
-} 
-</​code>​ 
-</​spoiler>​ 
-==== Attributs d'un fil ====  
- 
-Les attributs sont un moyen de spécifier un comportement différent du comportement par défaut. Quand un thread est créé avec   ​pthread_create ​  peut spécifier les attributs de ce fil. Les attributs par défaut sont suffisantes pour la plupart des applications. Avec un attribut peut changer: 
-  * Etat: unifié ou amovible 
-* la politique d'​attribution de processeur pour le thread respectif (round robin, FIFO ou par défaut du système) 
-  * priorité (les personnes ayant la priorité la plus élevée seront planifiées,​ en moyenne, plus souvent) 
-  * taille et adresse de départ de la pile 
- 
-Vous pouvez trouver plus de détails dans [[so: labs-2013: resources: thread_extra # fonctionne avec les attributs de thread | section dédiée]]. 
-==== Échec du processeur ==== 
- 
-Un thread exécute le droit d'​exécution sur un autre thread après l'un des événements suivants: 
- 
-  * Effectue un appel bloquant (demande d'E / S, synchronisation avec un autre thread) et le noyau décide qu'il profitable effectue un changement de contexte 
-  * Il a expiré le temps alloué par le noyau 
-  * cède volontairement le droit, en utilisant la fonction [[http://​linux.die.net/​man/​2/​sched_yield|sched_yield]:​ ]]:<code c> 
-int sched_yield(void);​ 
-</​code>​ 
- 
-Si d'​autres processeurs sont intéressés par le processeur, un processus capturera le processeur et s'il n'y a aucun processus en attente du processeur, le thread en cours continue l'​exécution. 
-==== Autres opérations ==== 
- 
-Si nous voulons nous assurer qu'un code d'​initialisation est exécuté une fois, nous pouvons utiliser la fonction: 
- 
-<code c> 
-pthread_once_t once_control = PTHREAD_ONCE_INIT;​ 
-int pthread_once(pthread_once_t *once_control,​ void (*init_routine) (void)); 
-</​code>​ 
- 
-Le but de ''​ pthread_once ''​ est de s'​assurer qu'un morceau de code (habituellement utilisé pour l'​initialisation) est exécuté une fois. L'​argument '​once_control'​ est un pointeur sur une variable initialisée avec ''​ PTHREAD_ONCE_INIT ''​. La première fois que cette fonction est appelée, elle appelle la fonction '​init_routine'​ et modifie la valeur de la variable '​once_control'​ pour se rappeler que l'​initialisation a eu lieu. Les appels suivants de cette fonction avec le même ''​ once_control ''​ ne feront rien. 
- 
-La fonction ''​ pthread_once ''​ renvoie 0 en cas d'​échec ou le code de défaut en cas d'​échec. 
- 
-Pour déterminer si deux identificateurs font référence au même fil, vous pouvez utiliser: 
- 
-<code c> 
-int pthread_equal(pthread_t thread1, pthread_t thread2); 
-</​code>​ 
-Les appels suivants sont disponibles pour apprendre / changer les priorités: 
- 
-<code c> 
-int pthread_setschedparam(pthread_t target_thread,​ int policy, const struct sched_param *param); 
-int pthread_getschedparam(pthread_t target_thread,​ int *policy, struct sched_param *param); 
-</​code>​ 
- 
-==== Compilation ==== 
-La bibliothèque libpthread doit également être spécifiée (utilisez donc l'​argument ''​ -lpthread ''​). 
-<note warning> Ne liez pas un programme à un seul thread à cette bibliothèque. Certains appels provenant de bibliothèques standard peuvent avoir des implémentations de débogage plus inefficaces ou plus difficiles lors de l'​utilisation de cette bibliothèque. </​note>​ 
- 
-==== Exemple ==== 
- 
-Voici un exemple simple dans lequel deux threads sont créés, chacun affichant un caractère un certain nombre de fois à l'​écran. 
- 
-<code c exemplu.c>​ 
-#include <​pthread.h>​ 
-#include <​stdio.h>​ 
- 
-/* parameter structure for every thread */ 
-struct parameter { 
- char character; /* printed character */ 
- int number; ​    /* how many times */ 
-}; 
- 
-/* the function performed by every thread */ 
-void* print_character(void *params) 
-{ 
- struct parameter *p = (struct parameter *) params; 
- int i; 
- 
- for (i = 0; i < p->​number;​ i++) 
- printf("​%c",​ p->​character);​ 
- printf("​\n"​);​ 
- 
- return NULL; 
-} 
- 
-int main() 
-{ 
- pthread_t fir1, fir2; 
- struct parameter fir1_args, fir2_args; 
- 
- /* create one thread that will print '​x'​ 11 times */ 
- fir1_args.character = '​x';​ 
- fir1_args.number = 11; 
- if (pthread_create(&​fir1,​ NULL, &​print_character,​ &​fir1_args)) { 
- perror("​pthread_create"​);​ 
- exit(1); 
- } 
- 
- /* create one thread that will print '​y'​ 13 times */ 
- fir2_args.character = '​y';​ 
- fir2_args.number = 13; 
- if (pthread_create(&​fir2,​ NULL, &​print_character,​ &​fir2_args)) { 
- perror("​pthread_create"​);​ 
- exit(1); 
- } 
- 
- /* wait for completion */ 
- if (pthread_join(fir1,​ NULL)) 
- perror("​pthread_join"​);​ 
- if (pthread_join(fir2,​ NULL)) 
- perror("​pthread_join"​);​ 
- 
- return 0; 
-} 
-</​code>​ 
-La commande utilisée pour compiler cet exemple sera: 
- 
-<code bash> 
-gcc -o exemplu exemplu.c -lpthread 
-</​code>​ 
- 
-===== Synchronisation des discussions ===== 
- 
-Pour synchroniser les threads, nous avons: 
-  * [[# Mutex | mutex]] 
-  * [[# Sémaphore | sémaphores]] 
-  * [[# variables de condition | variables de condition]] 
-  * [[#​Barrière | barrières]] 
-===== Mutex ===== 
- 
-Les mutations (verrous mutuellement exclus) sont des objets de synchronisation utilisés pour garantir ** l'​accès ** dans une section de code qui utilise ** les données partagées ** entre deux ou plusieurs threads. Un mutex a deux états possibles: ** Occupé ** et ** Gratuit **. Un mutex peut être occupé par un seul thread d'​exécution ** ** à la fois. Lorsqu'​un mutex est occupé par un thread, il ne peut plus être occupé par aucun autre thread. Dans ce cas, une demande d'​occupation provenant d'un autre thread va généralement ** bloquer ** le thread jusqu'​à ce que le mutex devienne libre. 
-==== Initialiser / Détruire un Mutex ==== 
- 
-Un mutex peut être initialisé / détruit de plusieurs manières: 
- 
-  * en utilisant une ** macrodéfinition ** <code c> 
-// Initialisation statique d'un mutex avec des attributs implicites 
-// NB: le mutex n'est pas publié, la durée de vie du mutex 
-// est la durée de vie du programme. 
-pthread_mutex_t mutex_static = PTHREAD_MUTEX_INITIALIZER;​ 
-</​code>​ 
- 
-  * initialisation avec ** attributs par défaut ** ([[http://​linux.die.net/​man/​3/​pthread_mutex_init|pthread_mutex_init]],​ [[http: //​linux.die.net/​man/​3/​pthread_mutex_destroy | pthread_mutex_destroy]]) <code c> 
-// signatures des fonctions d'​initialisation et de destruction de mutex: 
-int pthread_mutex_init ​  ​(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr); 
-int pthread_mutex_destroy(pthread_mutex_t *mutex); 
-  
-void initializare_mutex_cu_atribute_implicite() { 
-    pthread_mutex_t mutex_implicit;​ 
-    pthread_mutex_init(&​mutex_implicit,​ NULL); // atrr = NULL -> attributs par défaut 
-  
-    // ... en utilisant mutex ... 
-  
-    //​libération de mutex 
-        pthread_mutex_destroy(&​mutex_implicit);​ 
-} 
-</​code>​ 
- 
-  * Initialisation avec attributs explicites 
-<spoiler Détails pour l'​initialisation avec des attributs explicites>​ 
-<code c> 
-// NB: Fonction pthread_mutexattr_settype et macro PTHREAD_MUTEX_RECURSIVE 
-// sont uniquement disponibles si _XOPEN_SOURCE est défini à une valeur> = 500 
-// ** AVANT ** pour inclure <​pthread.h>​. 
-// Pour plus de détails, consultez feature_test_macros (7). 
-  
-#defines _XOPEN_SOURCE 500 
-#include <​pthread.h>​ 
-  
-void initializare_mutex_recursiv() { 
-    // définit les attributs, les initialise et marque le type comme récursif. 
-    pthread_mutexattr_t attr; 
-    pthread_mutexattr_init(&​attr);​ 
-    pthread_mutexattr_settype(&​attr,​ PTHREAD_MUTEX_RECURSIVE);​ 
-  
-    // définit un mutex récursif, initialisez-le avec les attributs définis précédemment 
-    pthread_mutex_t mutex_recursiv;​ 
-    pthread_mutex_init(&​mutex_recursiv,​ &attr); 
-  
-    // libère les attributs d'​attribut après la création d'un mutex 
-    pthread_mutexattr_destroy(&​attr);​ 
-  
-    // ... en utilisant mutex ... 
-  
-    //​libération de mutex 
-    pthread_mutex_destroy(&​mutex_recursiv);​ 
-} 
-</​code>​ 
- 
-</​spoiler>​ 
- 
-<note important> ​ Le mutex doit être ** gratuit ** pour être ** détruit **. Sinon, la fonction retournera le code d'​erreur ''​ EBUSY ''​. Le retour de ''​ 0 ''​ signifie le succès de l'​appel. </​note>​ 
-==== Types de mutex ==== 
- 
-A l'aide des attributs d'​initialisation,​ vous pouvez créer des mutex avec des propriétés spéciales: 
- 
-  * activation ** [[http://​en.wikipedia.org/​wiki/​Priority_inheritance priorité prioritaire]] ** (// héritage priorité //) pour empêcher ** [[http: //​en.wikipedia. org / wiki / Priority_inversion | inversion de priorité]] ** (// inversion de priorité //). Il existe trois protocoles d'​héritage prioritaire:​ 
-   * ''​ PTHREAD_PRIO_NONE ''​ - ** ** n'​hérite pas de la priorité lorsque nous possédons le mutex créé avec cet attribut 
-    * ''​ PTHREAD_PRIO_INHERIT ''​ - si un mutex est créé avec cet attribut et si des threads d'​exécution sont bloqués sur ce mutex, héritez de la priorité du thread avec ** priorité la plus élevée ** 
-    * ''​ PTHREAD_PRIO_PROTECT ''​ - Si le thread actuel a un ou plusieurs mutex, il s'​exécutera à maximum de spécifié pour tous les mutex possédés. 
- 
-<​spoiler>​ 
-<code c> 
-#define _XOPEN_SOURCE 500 
-#include <​pthread.h>​ 
- 
-int pthread_mutexattr_getprotocol(const pthread_mutexattr_t *attr, int *protocol); 
-int pthread_mutexattr_setprotocol(pthread_mutexattr_t *attr, int protocol); 
-</​code>​ 
- 
-  * mode de comportement à ** prise de contrôle récursive ** du mutex 
-    * ''​ PTHREAD_MUTEX_NORMAL ''​ - ** aucun ** contrôle n'est effectué, une prise récursive mène à ** // impasse // ** 
-    * ''​ PTHREAD_MUTEX_ERRORCHECK ''​ - les vérifications sont effectuées,​ les prises récursives conduisent à ** l'​erreur ** 
-    * ''​ PTHREAD_MUTEX_RECURSIVE ''​ - les mutex peuvent être pris de manière récursive mais doivent ** être libérés le même nombre de fois **. 
- 
-<code c> 
-#define _XOPEN_SOURCE 500 
-#include <​pthread.h>​ 
- 
-pthread_mutexattr_gettype(const pthread_mutexattr_t *attr, int *protocol); 
-pthread_mutexattr_settype(pthread_mutexattr_t *attr, int protocol); 
-</​code>​ 
-</​spoiler>​ 
- 
-==== Occupation / Libération d'un mutex ==== 
- 
-Fonctions de blocage / libération de mutex ([[[http://​linux.die.net/​man/​3/​pthread_mutex_lock|pthread_mutex_lock]]],​ [[http: //​linux.die.net/​man/​3/​pthread_mutex_unlock | pthread_mutex_unlock]]):​ 
- 
-<code c> 
-int pthread_mutex_lock (pthread_mutex_t * mutex); 
-int pthread_mutex_unlock (pthread_mutex_t * mutex); 
-</​code>​ 
- 
-Si le mutex est ** libre ** au moment de l'​appel,​ il sera occupé par la ligne appelante et la fonction reviendra immédiatement. Si le mutex est occupé par un ** autre **, l'​appel sera verrouillé jusqu'​à ce que le mutex soit libéré. Si le mutex est déjà occupé par ** le thread d'​exécution en cours ** (verrou récursif), le comportement de la fonction est dicté par le type de mutex: 
- 
-^ Type de mutex ^ Verrouillage récursif ^ Déverrouiller ^ 
-| ''​ PTHREAD_MUTEX_NORMAL ''​ impasse | libère mutex | 
-| ''​ PTHREAD_MUTEX_ERRORCHECK ''​ erreur de retour libère mutex | 
-| ''​ PTHREAD_MUTEX_RECURSIVE ''​ incrémente le nombre d'​occupants | décrémente le nombre d'​occupants (zéro libère le mutex) | 
-| ''​ PTHREAD_MUTEX_DEFAULT ''​ impasse | libère mutex | 
- 
-Il n'y a pas d'​ordre FIFO pour occuper un mutex. ** L’une des ** broches en attente d’un mutex à déverrouiller peut le capturer. 
-==== Le test non bloquant d’occupation d’un mutex ==== 
- 
-Pour essayer d'​occuper un mutex ** sans attendre ** pour le libérer s'il est déjà occupé, appelez [[http://​linux.die.net/​man/​3/​pthread_mutex_trylock|pthread_mutex_trylock]]:​ 
- 
-<code c> 
-int pthread_mutex_trylock(pthread_mutex_t *mutex); 
-</​code>​ 
- 
-Exemple: 
-<code c> 
-int rc = pthread_mutex_trylock(&​mutex);​ 
-if (rc == 0) { 
-    /* successfully aquired the free mutex */ 
-} else if (rc == EBUSY) { 
-    /* mutex was held by someone else 
-       ​instead of blocking we return EBUSY */ 
-} else { 
-    /* some other error occured */ 
-} 
-</​code>​ 
-==== Exemple d'​utilisation de mutex ==== 
- 
-Exemple d'​utilisation d'un mutex pour sérialiser l'​accès à la variable globale global_counter:​ 
- 
-<code c> 
-#include <​stdio.h>​ 
-#include <​pthread.h>​ 
-  
-#define NUM_THREADS 5 
-  
-/* global mutex */ 
-pthread_mutex_t mutex; 
-int global_counter = 0; 
-  
-void *thread_routine(void *arg)  
-{    ​ 
-    /* acquire global mutex */ 
-    pthread_mutex_lock(&​mutex);​ 
-  
-    /* print and modify global_counter */ 
-    printf("​Thread %d says global_counter=%d\n",​ (int) arg, global_counter);​ 
-    global_counter++;​ 
-  
-    /* release mutex - now other threads can modify global_counter */ 
-    pthread_mutex_unlock(&​mutex);​ 
-    ​ 
-    return NULL; 
-} 
-  
-int main(void) ​ 
-{ 
-    int i; 
-    pthread_t tids[NUM_THREADS];​ 
-  
-    /* init mutex once, but use it in every thread */ 
-    pthread_mutex_init(&​mutex,​ NULL); 
-  
-    /* all threads execute thread_routine 
-       as args to the thread send a thread id  
-       ​represented by a pointer to an integer */ 
-    for (i = 0; i < NUM_THREADS;​ i++) 
-        pthread_create(&​tids[i],​ NULL, thread_routine,​ (void *) i); 
-  
-    /* wait for all threads to finish */ 
-    for (i = 0; i < NUM_THREADS;​ i++) 
-        pthread_join(tids[i],​ NULL); 
-  
-    /* dispose mutex */ 
-    pthread_mutex_destroy(&​mutex);​ 
-    ​ 
-    return 0; 
-} 
-</​code>​ 
- 
-<code bash> 
-so@spook$ gcc -Wall mutex.c -lpthread 
-so@spook$ ./​a.out ​ 
-Thread 1 says global_counter=0 
-Thread 2 says global_counter=1 
-Thread 3 says global_counter=2 
-Thread 4 says global_counter=3 
-Thread 0 says global_counter=4 
-</​code>​ 
- 
-===== Futex ===== 
- 
-Les mutex des exécutions POSIX sont implémentés avec ** // futex // ** pour des raisons de performances. 
- 
-L’optimisation consiste à tester et à paramétrer de manière atomique la valeur du mutex (par une instruction // test-and-set-lock //) dans l’espace utilisateur,​ ** éliminant ainsi l’interruption du noyau ** en cas de dans lequel ** aucun ** blocage n'est requis. 
- 
-<spoiler Détails sur les futex> 
-Le // futex // nom vient de // Fast User-space muTEX //. L'​idée derrière la mise en œuvre de // futex // était ** d'​optimiser ** l'​occupation mutex si elle ** n'est pas déjà occupée **. Si le mutex n'est pas occupé, il le sera sans que le processus ne le bloque. Dans ce cas, et il n'est pas nécessaire de bloquer, il n'est pas nécessaire que le processus entre en mode noyau (pour entrer dans une file d'​attente). L’optimisation consiste à tester et à paramétrer de manière atomique la valeur du mutex (par une instruction // test-and-set-lock //) dans l’espace utilisateur,​ ** éliminant ainsi l’interruption du noyau ** en cas de dans lequel ** aucun ** blocage n'est requis. 
- 
-// Futex // peut être n'​importe quelle variable dans une zone de mémoire partagée entre plusieurs threads ou processus. Ainsi, les opérations réelles avec // futex // sont effectuées par la fonctionnalité ''​ do_futex '',​ disponible en incluant l'​en-tête ''​ linux / futex.h ''​. Sa signature ressemble à ceci: 
- 
-<code c> 
-long do_futex(unsigned long uaddr, int op, 
-              int val, unsigned long timeout, unsigned long uaddr2, int val2); 
-</​code>​ 
- 
-Si le blocage est requis, ''​ do_futex ''​ fera un appel système - ''​ sys_futex ''​. // Futex // peut être utile (et doit parfois être explicitement utilisé) en cas de synchronisation de processus, étant alloué en variables à partir de zones de mémoire partagée entre ces processus. 
- 
-</​spoiler>​ 
-===== Semafor ===== 
- 
-Les sémaphores sont des objets de synchronisation qui représentent une généralisation de mutex dans la mesure où ** enregistre le nombre d'​opérations de libération ** (incrémentielles) effectuées sur eux. En fait, un feu de signalisation est un atome qui augmente / diminue. La valeur d'un feu de signalisation ne peut pas tomber en dessous de 0. Si le feu de signalisation est à 0, l'​opération de décrémentation sera verrouillée jusqu'​à ce que le feu devienne strictement positif. Les mutex peuvent donc être considérés comme des sémaphores binaires. 
- 
-Les drapeaux POSIX sont de deux types: 
-  * 'avec noms' '- généralement utilisé pour la synchronisation entre processus distincts; 
-  * ''​ nonsense ''​ - qui peut être utilisé pour synchroniser les threads du même processus ou entre processus - à condition que le sémaphore se trouve dans une zone de mémoire partagée. 
- 
-Les différences entre les espaces de noms de premier nom et non nommés apparaissent dans les fonctions de création et de destruction,​ les autres fonctions étant identiques. 
-  * Les deux types de feux de signalisation sont représentés dans le code par le type ''​ sem_t ''​. 
-  * Les étiquettes de nom sont identifiées au niveau du système via une chaîne de caractères ''​ '/ nom' ''​. 
-  * les fichiers d'​en-tête requis sont ''​ <​fcntl.h>​ '',​ ''​ <sys / types.h> ''​ et ''​ <​semaphore.h>​ ''​. 
- 
-Les opérations pouvant être effectuées sur les sémaphores POSIX sont multiples: 
- 
-==== Mémoires de noms - Initialisation / Désinitialisation ==== 
- 
-<code c>  
-/* use named semaphore to synchronize processes */ 
-/* open */ 
-sem_t* sem_open(const char *name, int oflag); ​                                 
-/* create if oflag has O_CREAT set */ 
-sem_t* sem_open(const char *name, int oflag, mode_t mode, unsigned int value); 
-  
-/* close named semaphore */ 
-int sem_close(sem_t *sem); 
-  
-/* delete a named semaphore from system */ 
-int sem_unlink(const char *name); 
- </​code>​ 
- 
-<spoiler Détails des fonctions sem_open>​ 
-Le comportement est similaire à l'​ouverture de fichier. Si l'​indicateur ''​ O_CREAT ''​ est présent, la deuxième forme de la fonction doit être utilisée, en spécifiant les autorisations et la valeur initiale. 
- 
-Les seules possibilités pour le deuxième argument sont les suivantes: 
-  * 0 - s'​allume s'il y a 
-  * O_CREAT - le sémaphore est créé s'il n'​existe pas; il s'​ouvre s'il existe 
-  * O_CREAT | O_EXCL - le sémaphore ** est créé ** seulement ** s'il n'​existe pas; une erreur est renvoyée si elle existe 
-</​spoiler>​ 
-==== Sémantique anonyme - Initialisation / Désinitialisation ==== 
- 
-/ ** 
-  * initialisation d'un feu de signalisation sans nom 
-  * sem - le sémaphore nouvellement créé 
-  * pshared - 0 si le sémaphore est partagé uniquement 
-             par les fils du processus actuel 
-           - non nul: feu de signalisation partagé avec d'​autres processus 
-             dans ce cas, '​ici'​ doit être attribué dans une zone 
-             ​mémoire partagée 
-  * valeur - la valeur initiale du feu 
-  * / 
-<code c> 
-int sem_init(sem_t *sem, int pshared, unsigned int value); 
-  
-/* close unnamed semaphore */ 
-int sem_destroy(sem_t *sem); 
-</​code>​ 
-</​spoiler> ​ 
-==== Opérations courantes sur les feux de signalisation ==== 
- 
-<code c>  
-/* increment/​release semaphore (V) */ 
-int sem_post(sem_t *sem); 
-  
-/* decrement/​acquire semaphore (P) */ 
-int sem_wait(sem_t *sem); 
-  
-/* non-blocking decrement/​acquire */ 
-int sem_trywait(sem_t *sem); 
-  
-/* getting the semaphore count */ 
-int sem_getvalue(sem_t *sem, int *pvalue); 
-</​code>​ 
- 
-==== Exemple d'​utilisation d'un sémaphore avec le nom ==== 
-<code c> 
-#include <​fcntl.h> ​          /* For O_* constants */ 
-#include <​sys/​stat.h> ​       /* For mode constants */ 
-#include <​semaphore.h>​ 
- 
-#include "​utils.h"​ 
- 
-#​define SEM_NAME "/​my_semaphore"​ 
- 
-int main(void) 
-{ 
- sem_t *my_sem; 
- int rc, pvalue; 
- 
- /* create semaphore with initial value of 1 */ 
- my_sem = sem_open(SEM_NAME,​ O_CREAT, 0644, 1);  
- DIE(my_sem == SEM_FAILED, "​sem_open failed"​);​ 
- 
- 
- /* get the semaphore */ 
- sem_wait(my_sem);​ 
- 
- /* do important stuff protected by the semaphore */ 
- rc = sem_getvalue(my_sem,​ &​pvalue);​ 
- DIE(rc == -1, "​sem_getvalue"​);​ 
- printf("​sem is %d\n", pvalue); 
- 
- /* release the lock */ 
- sem_post(my_sem);​ 
- 
- rc = sem_close(my_sem);​ 
- DIE(rc == -1, "​sem_close"​);​ 
- 
- rc = sem_unlink(SEM_NAME);​ 
- DIE(rc == -1, "​sem_unlink"​);​ 
- 
- return 0; 
-} 
-</​code>​ 
- 
-Le sémaphore sera créé dans ''​ / dev / shm ''​ et portera le nom ''​ sem.my_semaphore ''​. 
-===== Variables de condition ===== 
- 
-Les variables de condition fournissent un système de notification de thread d'​exécution permettant à un thread de se bloquer en attendant le signal d'un autre thread. L'​utilisation correcte des variables de condition implique un protocole coopératif entre les threads d'​exécution. 
- 
-Les mutex et les sémaphores permettent à ** d'​autres threads de bloquer **. Les variables de condition servent à bloquer ** le thread en cours ** jusqu'​à ce qu'une condition soit remplie. 
- 
-Les variables de condition sont des objets de synchronisation qui permettent à un thread de suspendre son exécution jusqu'​à ce qu'une condition (logique du prédicat) ** devienne vraie **. Lorsqu'​un thread d'​exécution détermine que le prédicat est devenu vrai, il signale la condition de variable, déverrouillant ainsi un ou tous les threads d'​exécution bloqués à cette condition de variable (par intention). 
- 
-Une condition variable doit toujours être utilisée ** avec un mutex ** pour éviter la course qui se produit lorsqu'​un thread se prépare à attendre la condition de variable après avoir évalué le prédicat logique et qu'un autre thread indique la condition de variable même avant le premier thread. blocage, perdant ainsi le signal. Par conséquent,​ les opérations de signalisation,​ les tests de condition logique et la condition de verrouillage à la condition doivent être effectués avec le mutex ** occupé ** associé à la variable de condition. La condition logique est testée sous la protection du mutex, et si elle n'est pas remplie, le fil appelant se bloque lui-même à la condition de variable, libérant le mutex de manière atomique. Au déverrouillage,​ un thread d'​exécution essaiera d'​occuper le mutex associé à la condition de variable. En outre, le test des prédicats logiques doit être effectué dans une boucle ** **, car si plusieurs fils sont libérés à la fois, un seul sera en mesure de gérer le mutex associé à la condition. Le reste attendra qu'il le libère, mais il se peut que le fil du mutex change la valeur du prédicat logique pendant que le mutex est en place. Pour cette raison, les autres fils doivent tester à nouveau le prédicat car sinon, son exécution commencerait en supposant que le prédicat vrai est en réalité faux. 
-==== Initialiser / détruire une variable de condition ==== 
- 
-L'​initialisation d'une variable de condition est effectuée à l'aide de la macro PTHREAD_COND_INITIALIZER ou de la fonction [[http://​linux.die.net/​man/​3/​pthread_cond_init|pthread_cond_init]]. La destruction d'une variable de condition est effectuée par [[http://​linux.die.net/​man/​3/​pthread_cond_destroy|pthread_cond_destroy]]. 
- 
-<code c> 
-// initialisation statique d'une variable de condition avec des attributs implicites 
-// NB: la variable condition n'est pas libérée, 
-// la durée de vie de la variable condition est la durée de vie du programme. 
-pthread_cond_t cond = PTHREAD_COND_INITIALIZER;​ 
-  
-// Signatures d'​initialisation et de libération des variables de condition: 
-int pthread_cond_init ​  ​(pthread_cond_t *cond, pthread_condattr_t *attr); 
-int pthread_cond_destroy(pthread_cond_t *cond); 
-</​code>​ 
- 
-Comme Mutex: 
-  * si le paramètre '​attr'​ est NULL, les attributs par défaut sont utilisés 
-  * il ne doit pas y avoir de fil en attente sur la variable de condition quand elle est détruite, sinon elle retourne ''​ EBUSY ''​. 
-==== Verrouiller une condition de variable ==== 
- 
-Pour suspendre l'​exécution et attendre une condition de variable, un fil appelle la fonction [[http://​linux.die.net/​man/​3/​pthread_cond_wait|pthread_cond_wait]]:​ 
- 
-<code c> 
-int pthread_cond_wait (pthread_cond_t * cond, pthread_mutex_t * mutex); 
-</​code>​ 
- 
-Le thread appelant doit ** être ** déjà associé au mutex au moment de l'​appel. La fonction '​pthread_cond_wait'​ va ** libérer ** le mutex et ** bloquer **, en attendant que la condition de variable soit marquée par un autre thread. Les deux opérations sont ** atomiques **. Lorsque la condition de variable est signalée, il essaiera d’occuper le mutex associé et, après ** occupant **, l’appel de fonction reviendra. Notez que le thread appelant peut être suspendu après le déverrouillage en attendant que le mutex associé soit occupé, tandis que le prédicat logique, true lors du déverrouillage du thread, peut être modifié par d'​autres threads. Par conséquent,​ l'​appel ''​ pthread_cond_wait ''​ doit être effectué dans une boucle où la valeur prédictive de vérité associée à la variable de condition est testée pour garantir une sérialisation correcte des threads. Un autre argument en faveur du test de boucle du prédicat logique est qu'un appel ''​ pthread_cond_wait ''​ peut être ** interrompu ** par un signal asynchrone (voir laboratoire du signal) avant que le prédicat logique ne devienne vrai. Si les unités d'​exécution en attente de la condition de variable ne testent pas à nouveau le prédicat logique, elles poursuivent leur exécution en supposant que cela est vrai. 
-==== Verrouiller une condition variable avec timeout ==== 
- 
-Afin de suspendre l'​exécution et d'​attendre une condition de variable, au plus tard à une heure spécifiée,​ un thread d'​exécution appellera [[http://​linux.die.net/​man/​3/​pthread_cond_timedwait|| pthread_cond_timedwait]]:​ 
- 
-<code c> 
-int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, ​ 
-                           const struct timespec *abstime); 
-</​code>​ 
- 
-La fonction se comporte de la même façon que ''​ pthread_cond_wait '',​ sauf que si la condition de variable n'est pas signalée avant '​abstime'​ ', la ligne appelante est déverrouillée et, après avoir occupé le mutex associé, la fonction retourne erreur ''​ ETIMEDOUT ''​. Le paramètre '​abstime'​ est absolu et représente le nombre de secondes écoulées depuis le 1er janvier 1970 à 00:00. 
-==== Déverrouiller un seul thread verrouillé dans une condition de variable ==== 
- 
-Pour déverrouiller un seul thread de verrouillage sur une variable de condition, la variable de condition sera signalée à l'aide de [[http://​linux.die.net/​man/​3/​pthread_cond_signal|pthread_cond_signal]]:​ 
- 
-<code c> 
-int pthread_cond_signal (pthread_cond_t * cond); 
-</​code>​ 
- 
-Si aucun thread n'​attend la variable, l'​appel de fonction ne fonctionne pas et la signalisation ** perd **. Si plusieurs threads attendent la variable variable, un seul d'​entre eux sera déverrouillé. Le choix du thread à déverrouiller est effectué par le concepteur de thread. On ne peut pas supposer que les threads en attente seront déverrouillés dans l'​ordre dans lequel leur attente commence. Le thread appelant doit avoir le ** mutex ** associé à la variable de condition lors de l'​appel de cette fonction. ​ 
- 
-Exemple: 
-<code c> 
-pthread_mutex_t count_lock; 
-pthread_cond_t ​ count_nonzero;​ 
-unsigned ​       count; 
-  
-void decrement_count() { 
-    pthread_mutex_lock(&​count_lock);​ 
-    while (count == 0) 
-        pthread_cond_wait(&​count_nonzero,​ &​count_lock);​ 
-    count = count - 1; 
-    pthread_mutex_unlock(&​count_lock);​ 
-} 
-  
-void increment_count() { 
-    pthread_mutex_lock(&​count_lock);​ 
-    count = count + 1; 
-    pthread_cond_signal(&​count_nonzero);​ 
-    pthread_mutex_unlock(&​count_lock);​ 
-} 
-</​code>​ 
-==== Déverrouillez tous les threads verrouillés avec une variable de condition ==== 
-Pour déverrouiller tous les threads d'​exécution bloqués au niveau d'une variable de condition, la variable de condition est marquée à l'aide de [[http://​linux.die.net/​man/​3/​pthread_cond_broadcast|pthread_cond_broadcast]]:​ 
- 
-<code c> 
-int pthread_cond_broadcast (pthread_cond_t * cond); 
-</​code>​ 
- 
-Si aucun thread n'​attend la variable, l'​appel de fonction ne fonctionne pas et la signalisation ** perd **. Si la variable de condition attend des threads d'​exécution,​ tout sera déverrouillé,​ mais ** sera en concurrence ** pour occuper le mutex associé à la variable condition. Le thread appelant doit avoir le mutex associé à la condition de variable lors de l'​appel de cette fonctionnalité. 
-==== Exemple d'​utilisation de variables de condition ==== 
- 
-Le programme suivant utilise une barre pour synchroniser les threads du programme. La barrière est implémentée à l'aide d'une variable de condition. 
- 
-<code c> 
-#include <​stdio.h>​ 
-#include <​pthread.h>​ 
-  
-#define NUM_THREADS 5 
-  
-// implémente une barrière * non réutilisable avec des variables de condition 
-struct my_barrier_t { 
-    // mutex utilisé pour sérialiser l'​accès aux données de barrière internes 
-    pthread_mutex_t lock; 
-    ​ 
-    // la variable de condition qui devrait arriver à tous les threads 
-    pthread_cond_t cond; 
-    ​ 
-    // le nombre de threads qui doivent venir pour libérer la barrière 
-    int nr_still_to_come;​ 
-}; 
-  
-structure my_barrier_t bar; 
-  
-void my_barrier_init (struct my_barrier_t * bar, int nr_still_to_come) { 
-    pthread_mutex_init (& bar-> lock, NULL); 
-    pthread_cond_init (& bar-> cond, NULL); 
-    ​ 
-    // combien de threads sont attendus à la barrière 
-    bar-> nr_still_to_come = nr_still_to_come;​ 
-} 
-  
-void my_barrier_destroy (struct my_barrier_t * bar) { 
-    pthread_cond_destroy (& bar-> cond); 
-    pthread_mutex_destroy (& bar-> serrure); 
-} 
-  
-void * thread_routine (void * arg) { 
-    int thd_id = (int) arg; 
-  
-    // Avant de travailler avec les données de la barrière interne, il faut prendre le mutex 
-    pthread_mutex_lock (& bar.lock); 
-  
-    printf ("thd% d: avant la barrière \ n", thd_id); 
-  
-    // Sommes-nous le dernier thread d'​exécution qui est arrivé à la barrière? 
-    int is_last_to_arrive = (bar.nr_still_to_come == 1); 
-    // décrémente le nombre de threads attendus à la barrière 
-    bar.nr_still_to_come --; 
-  
-    // Alors qu'il y a d'​autres threads qui n'ont pas atteint la barrière, nous attendons. 
-    while (bar.nr_still_to_come! = 0) 
-        // le mutex est libéré automatiquement avant le début de l'​attente 
-        pthread_cond_wait (& bar.cond, & bar.lock); 
-  
-    // le dernier thread de la barrière signalera les autres threads 
-    if (is_last_to_arrive) { 
-        printf ("​laissez l'​inondation dans \ n"); 
-        pthread_cond_broadcast (& bar.cond); 
-    } 
-  ​ 
-    printf ("thd% d: après la barrière \ n", thd_id); 
-  
-    // La sortie du mutex enlève automatiquement le mutex à quitter 
-    pthread_mutex_unlock (& bar.lock); 
-  ​ 
-    return NULL; 
-} 
-  
-int main (void) 
-    int i; 
-    pthread_t indique [NUM_THREADS];​ 
-  
-    my_barrier_init (& bar, NUM_THREADS);​ 
-  
-    for(i = 0; i <​NUM_THREADS;​ i ++) 
-        pthread_create (& tids [i], NULL, thread_routine,​ (void *) i); 
-  
-    for (i = 0; i <​NUM_THREADS;​ i ++) 
-        pthread_join (informations [i], NULL); 
-  
-    my_barrier_destroy (& bars); 
-    ​ 
-    return 0; 
-} 
-</​code>​ 
- 
-<code bash> 
-so @ spook $ gcc -Wall cond_var.c -pthread 
-donc @ spook $ ./a.out 
-thd 0: avant la barrière 
-2e 2: devant la barrière 
-thd 3: devant la barrière 
-thd 4: devant la barrière 
-1 er: devant la barrière 
-    laisser l'​inondation dans 
-1 er: après la barrière 
-2e 2: après la barrière 
-3 ème: après la barrière 
-thd 4: après la barrière 
-thd 0: après la barrière 
-</​code>​ 
-La mise en œuvre du programme montre: 
-  * l'​ordre dans lequel les threads sont planifiés ** ** n'est pas nécessairement celui de leur création 
-  * l'​ordre dans lequel les exécutions en attente d'une variable de condition ** sont réveillées ** n'est pas nécessairement l'​ordre dans lequel elles étaient en attente. 
- 
-===== Barrière ===== 
- 
-La norme POSIX définit également un ensemble de fonctions et de structures de données de barrière. Ces fonctions sont disponibles si la macro ''​ _XOPEN_SOURCE ''​ est définie à une valeur> = 600. 
- 
- 
-==== Initialiser / Détruire une barrière ==== 
- 
-La barrière sera initialisée à l'aide de [[http://​linux.die.net/​man/​3/​pthread_barrier_init|pthread_barrier_init]] et sera détruite à l'aide de [[http://​linux.die.net/​man/​3/​pthread_barrier_destroy|pthread_barrier_destroy ]]. 
- 
-<code c> 
-// pour utiliser les fonctions de barrière doit être défini 
-// _XOPEN_SOURCE à une valeur> = 600. Pour plus de détails, voir feature_test_macros (7). 
-#define _XOPEN_SOURCE 600 
-#include <​pthread.h>​ 
-  
-// attr -> un ensemble d'​attributs,​ peut être NULL (les attributs par défaut sont utilisés) 
-// count -> le nombre de threads qui doivent arriver 
-// à la barrière pour le libérer 
-int pthread_barrier_init (pthread_barrier_t * barrière, 
-                         const pthread_barrierattr_t * attr, 
-                         ​unsigned count); 
-  
-// il ne doit y avoir aucun thread en attente à la barrière 
-// avant d'​appeler la fonction _destroy, sinon EBUSY retourne 
-// ne détruis pas la barrière. 
-int pthread_barrier_destroy (pthread_barrier_t * barrier); 
-</​code>​ 
-==== En attente d'une barrière ==== 
- 
-L’attente de la barrière est terminée en appelant [[http://​linux.die.net/​man/​3/​pthread_barrier_wait|pthread_barrier_wait]]:​ 
- 
-<code c> 
-#define _XOPEN_SOURCE 600 
-#include <​pthread.h>​ 
-int pthread_barrier_wait(pthread_barrier_t *barrier); 
-</​code>​ 
- 
-Si la barrière a été créée avec ''​ compte = N '',​ les premiers threads ''​ N-1 ''​ qui appellent ''​ pthread_barrier_wait ''​ sont bloqués. Lorsque le dernier (N) apparaît, il déverrouille tous les threads N-1. La fonction ''​ pthread_barrier_wait ''​ renvoie trois valeurs: 
-  * ''​ EINVAL ''​ - si la barrière n'est pas initialisée (seul défaut défini) 
-  * ''​ PTHREAD_BARRIER_SERIAL_THREAD ''​ - en cas de succès, un seul thread renverra cette valeur - il n'est pas spécifié quel thread est exécuté (il n'est pas nécessaire que le dernier ait atteint la barrière) 
-  * ''​ 0 ''​ - valeur renvoyée en cas de succès par les autres threads N-1. 
-==== Exemple d'​utilisation de la barrière ==== 
- 
-Avec les barrières POSIX, le programme ci-dessus peut être simplifié: 
- 
-<code c> 
-#define _XOPEN_SOURCE 600 
-#include <​pthread.h>​ 
-#include <​stdio.h>​ 
- 
-#define NUM_THREADS 5 
-  
-pthread_barrier_t barrier; 
-  
-void *thread_routine(void *arg) { 
-    int thd_id = (int) arg; 
-    int rc; 
-  
-    printf("​thd %d: before the barrier\n",​ thd_id); 
-  
-    // tous les threads en attente à la barrière. 
-    rc = pthread_barrier_wait(&​barrier);​ 
-    if (rc == PTHREAD_BARRIER_SERIAL_THREAD) { 
-        // un seul thread (éventuellement le dernier) retournera PTHREAD_BARRIER_SERIAL_THREAD 
-        // le reste des discussions renvoie 0 en cas de succès. 
-        printf(" ​  let the flood in\n"​); ​ 
-    } 
-    ​ 
-    printf("​thd %d: after the barrier\n",​ thd_id); 
-  ​ 
-    return NULL; 
-} 
-  
-int main (void) 
-{ 
-    int i; 
-    pthread_t tids[NUM_THREADS];​ 
-  
-    // La barrière est initialisée une fois et utilisée par tous les threads 
-    pthread_barrier_init(&​barrier,​ NULL, NUM_THREADS);​ 
-  
-    // les exécutions exécuteront le code de fonction thread_routine. 
-    // au lieu d'un pointeur sur des données utiles, il est envoyé dans le dernier argument 
-    // un entier - l'​identifiant du thread 
-   for (i = 0; i < NUM_THREADS;​ i++) 
-        pthread_create(&​tids[i],​ NULL, thread_routine,​ (void *) i); 
-  
-    // nous nous attendons à ce que tous les threads se terminent 
-    for (i = 0; i < NUM_THREADS;​ i++) 
-        pthread_join(tids[i],​ NULL); 
-  
-    // Nous libérons les ressources de la barrière 
-    pthread_barrier_destroy(&​barrier);​ 
-  ​ 
-    return 0; 
-} 
-</​code>​ 
-<code bash> 
-so@spook$ gcc -Wall barrier.c -lpthread 
-so@spook$ ./​a.out ​ 
-thd 0: before the barrier 
-thd 2: before the barrier 
-thd 1: before the barrier 
-thd 3: before the barrier 
-thd 4: before the barrier 
-   let the flood in 
-thd 4: after the barrier 
-thd 2: after the barrier 
-thd 3: after the barrier 
-thd 0: after the barrier 
-thd 1: after the barrier 
-</​code>​ 
- 
-====== Exercices de laboratoire ====== 
- 
-===== Exercice 0 - Jeu interactif (2p) ===== 
- 
-  * Détails [[http://​ocw.cs.pub.ro/​courses/​so/​meta/​notare#​joc_interactiv|jeu]].. 
- 
-===== Linux (9p) ===== 
- 
-Pour résoudre le labo, veuillez cloner [[https://​www.github.com/​upb-fils/​sde|repository]]. si vous en avez déjà un, lancez svp ''​ git pull ''​. 
- 
-<note tip> Pour vous aider à mettre en œuvre les exercices de laboratoire,​ il existe un fichier ''​ utils.h ''​ avec des fonctions utiles dans le répertoire ''​ utils ''​ de l'​archive. </​note>​ 
- 
-<​note>​ Pour installer les pages de manuel de '​pthreads'​ 
-<code bash> sudo apt-get install manpages-posix manpages-posix-dev </​code>​ 
-</​note>​ 
- 
-==== Exercice 1 - Pile de fils (2p) ==== 
- 
-Allez dans le répertoire ''​ 1-th_stack ''​ et inspectez la source, puis compilez et exécutez le programme. Suivez ''​ pmap ''​ ou '​procfs'​ 'pour modifier l'​espace d'​adressage du programme: 
-<code bash> 
-watch -d pmap $(pidof th_stack) 
-watch -d cat /​proc/​$(pidof th_stack)/​maps 
-</​code> ​ 
- 
-Les zones de mémoire de 8 Mo (8192 Ko) créées après chaque appel pthread_create représentent les nouvelles // piles // allouées par la bibliothèque libpthread pour chaque thread. Notez qu'une page (4Ko) avec la protection '%% --- p %%' (PROT_NONE, private - visible dans ''​ procfs ''​) joue le rôle de %% "%% garde de page %%" %%. 
- 
-La raison pour laquelle le programme ne se termine pas est la présence d'une fonction "while (1)" dans la fonction thread. Utilisez ''​ Ctrl + C ''​ pour terminer le programme. 
-==== Exercice 2 - Processus d'​exécution vs processus (2p) ==== 
- 
-Accédez au répertoire '​2-th_vs_proc'​ et inspectez les sources. Les deux programmes simulent un serveur qui crée des threads / processus. Compilez et exécutez les deux programmes un à la fois. 
- 
-Lors de l'​exécution,​ vous affichez dans une autre console le nombre de threads / processus créés dans les deux situations à l'aide de la commande ''​ ps-L-c <​nom_programme>​ ''​. 
- 
-<code bash> 
-ps -L -C threads 
-ps -L -C processes 
-</​code>​ 
- 
-Vérifiez ce qui se passe si, à un moment donné, un thread en cours de fonctionnement meurt (ou un processus, en fonction de l'​exécutable que vous testez). Testez en utilisant la fonction '​do_bad_task'​ sur chaque 4ème thread / processus. 
-==== Exercice 3 - Sécurité du fil (2p) ==== 
- 
-<note important>​ 
-Etant donné que la machine virtuelle spook n'a qu'un seul noyau virtuel, le prochain exercice doit être effectué sur la machine physique pour permettre à plusieurs threads de s'​exécuter simultanément. 
-</​note>​ 
- 
-Allez dans le répertoire ''​ 3-safety ''​ et inspectez la source ''​ malloc.c ''​. Les fonctions ''​ thread_function ''​ et ''​main''​ NOT sont thread-safe par rapport aux variables ''​global_storage''​ et ''​function_global_storage''​(voir la signification de [[http: //​en.wikipedia .org / wiki / Thread_safety | thread safety]] ). Il existe un [[https://​en.wikipedia.org/​wiki/​Race_condition | Race condition]] entre les deux threads créés en incrémentant la variable function_global_storage déclarée dans la fonction thread_function et une autre condition d'​exécution entre tous les threads de processus lors de l'​incrémentation de la variable globale ''​global_storage''​. 
-<note tip> 
-"​Helgrind"​ est un utilitaire très utile, capable de détecter automatiquement ces conditions de concurrence. Nous pouvons l'​utiliser comme suit: 
-<code bash> 
-valgrind --tool=helgrind ./mutex 
-</​code>​ 
-</​note>​ 
-* nous allons nous concentrer sur la résolution de TODO1 sur un mutex exécutable 
-''​ TODO 1 '':​ pour résoudre ces deux conditions de concurrence,​ appelez la fonction de plus-value en mode thread_safe avec l'aide de l'API fournie par critical.h 
- 
-Le fichier ''​ malloc.c ''​ crée NUM_THREADS qui alloue de la mémoire en 1000 tours. Il existe des chances que les threads exécutent des appels "​malloc"​ concurrents. 
-Une fois que vous avez résolu le problème, compilez et exécutez plusieurs fois. Nous remarquons que le programme fonctionne avec succès. Pour faire des vérifications supplémentaires,​ nous lançons à nouveau ''​ helgrind '':​ 
- 
-<code bash> 
-valgrind --tool=helgrind ./mutex 
-</​code>​ 
- 
-Nous notons que ni '​helgrind'​ ne rapporte aucune erreur, ce qui a pour conséquence que la fonction malloc est thread-safe. (même s'il n'est pas protégé par l'API fournie) 
-Pour être sûr, nous devons examiner les pages de manuel et le code source. 
- 
-<note important>​ 
-Il est important de savoir que certaines fonctions sont thread-safe et que d'​autres ne le sont pas. Recherchez une liste des fonctionnalités non liées aux threads dans la page de manuel [[http://​man7.org/​linux/​man-pages/​man7/​pthreads.7.html|pthreads(7)]] dans le ' Fonctions de thread-safe '. 
- 
-La fonction '​malloc'​ de l'​implémentation GLIBC ** est thread-safe **, comme indiqué dans la page de manuel [[http: //​man7.org/​linux/​man-pages/​man3/​malloc.3.html#​NOTES | malloc (3)]] (troisième paragraphe de la section "​NOTES"​) et visible dans le code source par la présence du champ "​mutex"​ dans [[https://​sourceware.org/​git/?​p=glibc.git ; a = blob; f = malloc / malloc.c; h = f361bad636167cf1680cb75b5098232c9232d771;​ hb = HEAD # l1672 | structure malloc_state]]. 
-</​note>​ 
- 
-''​ TODO 2 '':​ Implémente un spinlock en utilisant des opérations atomiques. 
-Les opérations atomiques existantes dans la norme GCC peuvent être consultées à l’adresse [[https://​gcc.gnu.org/​onlinedocs/​gcc/​_005f_005fatomic-Builtins.html | fonctions __atomiques]] 
- 
-Dans le fichier critical.c, vous devez renseigner les commentaires ''​ TODO 2 ''​ associés. 
- 
-Testez et exécutez plusieurs fois pour vérifier la cohérence de la variable globale: global_storage,​ l'​exécutable ''​ ./spin ''​. 
- 
-==== Exercice 4 - Bloqué (2p) ==== 
- 
- 
-Le fichier répertoire Inspectez ​  de blocked.c ​    ​4-bloqué ​   , compiler et exécuter binaire (répéter jusqu'​à un logiciel de blocage détecté). Le programme crée deux threads à la recherche d'un nombre magique, chacun dans son propre (pas nécessairement le nombre à trouver). Chaque fil pour chaque valeur de sa gamme, vérifiez si la demande: 
-  * Si oui, indiquez le champ     ​trouvé ​    de laisser l'​autre fil que l'on trouve le nombre recherché. 
-  * Dans le cas contraire, inspecter le champ     ​trouvé ​    ​l'​autre structure de fil pour voir si elle a déjà trouvé le numéro recherché. 
- 
-Déterminer le blocage de la cause, le programme de réparation et d'​expliquer la solution. Vous pouvez utiliser ​  ​Helgrind ​ , l'un des sites d'​outils de Valgrind ​    pour détecter le problème: <bash code> 
-$ valgrind --tool=helgrind ./blocked 
-</​code>​ 
- 
-<note tip> 
-Comme le montre et   ​Helgrind ​    Le problème est que les deux fils prennent deux années mutex dans l'​ordre inverse, le très susceptible de causer un [[https://​en.wikipedia.org/​wiki/​Deadlock|deadlock]]. 
-</​note>​ 
-==== Exercice 5 - Mise en œuvre comportement pthread_once (1p) ==== 
- 
-Vous fonctionnez l'​initialisation que vous voulez appeler une fois. À partir de la source ​  ​once.c ​  ​répertoire ​  ​5-once ​ , assurez-vous que la fonction ​  ​init_func ​  est appelée une fois. ** ** Vous ne pouvez pas modifier la fonction ​  ​init_func ​  ou utiliser ​  ​pthread_once ​ . 
- 
-Lisez à propos de la fonctionnalité [[http://​linux.die.net/​man/​3/​pthread_once | pthread_once]] et consultez la section sur [[#mutex | mutex]]. 
- 
-==== Exercice 6 - Mutex contre Spinlock (1p) ==== 
- 
-Nous voulons tester quelle variante est la plus efficace pour protéger l’incrément d’une variable. 
- 
-Allez dans le répertoire ''​ 6-spin '',​ inspectez et compilez la source ''​ spin.c ''​. La compilation donnera deux exécutables,​ l’un utilisant un mutex pour la synchronisation et l’autre un spinlock. 
- 
-Comparer les temps d'​exécution:​ 
-<code bash> 
-time ./mutex 
-time ./spin 
-</​code>​ 
- 
-<note tip> 
-Lorsqu'​un thread trouve le mutex occupé, il se verrouille. Lorsqu'​un thread trouve le spinlock occupé, il sera occupé. 
-</​note>​ 
- 
-===== Ressources utiles ===== 
- 
-[[http://​www.yolinux.com/​TUTORIALS/​LinuxTutorialPosixThreads.html | LinuxTutorialPosixThreads]] 
- 
-[[https://​computing.llnl.gov/​tutorials/​pthreads/​ | Programmation de threads POSIX]] 
  
sde/laboratoare/08.1586287430.txt.gz · Last modified: 2020/04/07 22:23 by diana.ghindaoanu
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0