TP threads POSIX


Les threads POSIX

et leurs extensions temps réel

Sommaire


Documentations :

ATTENTION :

I.1 Exercice 1 : exemple de base

Cet exercice présente les fonctions pthread_create et pthread_join.

Ce qu'il faut faire :

  1. Compléter (en utilisant pthread_create), puis compiler et exécuter le programme contenu dans le fichier Exo0.c .

    Pour compiler : gcc -o Exo0 -Wall Exo0.c -lpthread

    Le résultat de l'exécution est un affichage du type :

    
    ------ Thr (0x)a039afa0 (main) cree: (0x)b0081000 (i = 0)
    Thread (0x)b0081000 : CREE
    ------ Thr (0x)a039afa0 (main) cree: (0x)b0103000 (i = 1)
    Thread (0x)b0103000 : CREE
    ------ Thr (0x)a039afa0 (main) cree: (0x)b0185000 (i = 2)
    Thread (0x)b0185000 : CREE
    ------ Thr (0x)a039afa0 (main) cree: (0x)b0207000 (i = 3)
    Thread (0x)b0207000 : CREE
    ------main Fin de main (0x)a039afa0
    
    
  2. Pourquoi ne voit-on pas s'afficher les printf de la fonction Un_Thread ?

    Modifier le programme en utilisant pthread_join et vérifier que les printf de Un_Thread sont bien exécutés.

Un corrigé se trouve ici.

I.2 Exercice 2 : utilisation de variables conditionnelles

On veut écrire un programme dans lequel main, comme dans l'exercice précédent, attend la fin de tous les threads qu'il a créé. Mais on ajoute une contrainte :

pthread_join ne convient pas puisqu'elle impose un ordre sur la fin des threads dont on attend la terminaison.
On utilisera donc une variable conditionnelle sur laquelle se bloque main une fois qu'il a créé et lancé tous les threads.
Cette variable conditionnelle sera utilisée par les threads fils pour indiquer leur terminaison.

On donne un canevas de ce programme dans le fichier VarCond.c .

On vous demande :

  1. de compléter ce programme (cf. lignes comportant des ...),
  2. puis de le tester.
    Eventuellement, utiliser les commandes truss ou strace pour tracer l'exécution.
  3. Ce résultat est-il satisfaisant ? C'est à dire : les threads qui se sont terminés ont-ils tous été acquittés ?
    Indication : pour le vérifier, faire afficher la variable qui compte le nombre d'acquittements.
    Nous allons maintenant remédier à ce dysfonctionnement.
    Comment faire ?
    1. vérifier où se font les prises et les libérations du verrou Verrou :
      • Qui prend la main lorsque l'un des threads créés par main appelle pthread_cond_signal ?
      • Comment se passe la création des threads ?
    2. ajouter un autre verrou,
  4. Modifier le programme et vérifier que tous les threads sont bien acquittés après leur terminaison : la variable qui compte le nombre d'acquittements doit avoir comme valeur le nombre de threads qui se sont terminés.

I.3. Exercice 3 : attente temporisée

On va maintenant utiliser une variable conditionnelle pour mettre en oeuvre un schéma du type "delay until" en Ada.

Ce qu'il faut faire :

  1. Partir du canevas donné ici : delay_until.c .
  2. Utiliser l'horloge temps réel : clock_gettime(CLOCK_REALTIME, &time),
  3. Faire appel à la fonction pthread_cond_timedwait (&var_cond, &verrou, &time) de façon à réveiller périodiquement chacun des threads, cette période étant passée sur la ligne de commande,
Les résultats seront présentés comme suit :
                                  
tid 2 : date (s.ns) : 1192026591.432474182, compteur 1, delai 2
tid 3 : date (s.ns) : 1192026593.432828848, compteur 1, delai 4
tid 2 : date (s.ns) : 1192026593.440042764, compteur 2, delai 2
tid 3 : date (s.ns) : 1192026597.440020345, compteur 2, delai 4
tid 2 : date (s.ns) : 1192026595.440110345, compteur 3, delai 2
...
main (tid 1) fin de tid 2
tid 3 : date (s.ns) : 1192026617.490021326, compteur 7, delai 4
tid 3 : date (s.ns) : 1192026621.500019488, compteur 8, delai 4
tid 3 : date (s.ns) : 1192026625.510039568, compteur 9, delai 4
tid 3 : date (s.ns) : 1192026629.520014564, compteur 10, delai 4
main (tid 1) fin de tid 3
Un corrigé se trouve ici.

I.4. Exercice 4 : verrou à héritage de priorité

On va utiliser les extensions temps réel de la bibliothèque de threads POSIX pour faire hériter un thread de la priorité du verrou qu'il vient de prendre.
On met en jeu deux threads T0 et T1 et un verrou M1 tels que :
Priorité de M1 > Priorité de T1 > Priorité de T0.

Scénario :
main va créer un thread T0 et un verrou M1 dont l'attribut est PRIO_PROTECT. Ensuite main attend la fin de T0.
Le thread T0 va créer le thread T1, puis prendre le verrou M1 (dont il hérite la priorité). Ensuite il éléve la priorité de T1 à une priorité supérieure à la sienne. Mais T1 ne va PAS démarrer puisque T0 a hérité de la priorité de M1. Lorsque T0 rend M1 (unlock), T1 démarre. Le programme à compléter se trouve ici . La trace de l'exécution t ressembler à la suivante :

main : tid de main : 0x00000001
main : Prio. min et max en FIFO : 0 59
main : main-setshared-status = 0
main : mutexattr_setprotocol = 0
main : ceiling de M1 = 54
main : mutexattr_getprotocol = 32
protocole du mutex : PTHREAD_PRIO_PROTECT
Task 0 : tid de Task 0  : 0x00000002
Task 0 : AVANT lock(M1) - priorite = 0
Task 0 : lock, status = 0
Task 0 : apres lock(M1) - priorite = 0
policy : FIFO
Task 0 : creation de Task_1, tid  : 0x00000003
Task 0 : getschedparam pour T1 - priorite = 0
Task 0 : getschedparam pour T1 - sched = 1
policy : FIFO
Task 0 : getschedparam pour T1 - priorite = 52
Task 0 : getschedparam pour T1 - sched = 1
policy : FIFO
Task 0 : debut de calcul
Task 0 :  i = 0
Task 0 :  i = 1000000
Task 0 :  i = 2000000
Task 0 :  i = 3000000
Task 0 :  i = 4000000
Task 0 :  i = 5000000
Task 0 :  i = 6000000
Task 0 :  i = 7000000
Task 0 :  i = 8000000
Task 0 : fin de calcul
Task 1 (3) : debut de calcul
Task 1 :  i = 0
Task 1 :  i = 1000000
Task 1 :  i = 2000000
Task 1 :  i = 3000000
Task 1 :  i = 4000000
Task 1 :  i = 5000000
Task 1 :  i = 6000000
Task 1 :  i = 7000000
Task 1 :  i = 8000000
Task 1 (3): fin de calcul
Task 1 (3): essaie de prendre M1
Task 1 (3): trylock, status = 0
Task 1 (3): mutex M1 pris, va calculer
Task 1 (3): exit
Task 0 : unlock M1 -status = 0
Task 0 : priorite  = 0
Task 0 : join T1, status = 0
main : creation de Task_0, tid  : 0x00000002
main : join T0, status = 0
Un corrigé se trouve ici.

I.5. Exercice 5 : inversion de priorité

On va maintenant illustrer le problème de l'inversion de priorité.
On met en jeu trois threads T0, T1 et T2 tels que :
Priorité de T2 > Priorité de T1 > Priorité de T0.

On utilise aussi un verrou M1.

Scénario :

main va créer les trois threads threads et le verrou M1.
Activité des threads :


Déroulement du scénario :
Questions :
  1. En vous inspirant de l'exemple précédent, on vous demande de mettre en oeuvre ce scénario et d'en constater le dysfonctionnement.
  2. Donner au verrou les attributs convenables pour résoudre ce problème d'inversion de priorité. Exécuter le programme pour montrer le bon fonctionnement du scénario.
Scénario correct :
RAPPEL CORRIGES:

essai00.c .

delay_until.c .