TP threads POSIX
Les threads POSIX
et leurs extensions temps réel
Sommaire
Documentations :
ATTENTION :
-
Faire l'édition de liens avec -lpthread
et inclure le fichier pthread.h
-
Pour les options temps réel, ajouter -lrt
-
Eventuellement utiliser : #define _REENTRANT
avant le premier include, si problèmes.
Cet exercice présente
les fonctions pthread_create et
pthread_join.
Ce qu'il faut faire :
-
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
-
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.
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 :
-
main est averti de la fin d'un thread DES que celui-ci se termine.
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 :
-
de compléter ce programme (cf. lignes comportant des ...),
-
puis de le tester.
Eventuellement, utiliser les commandes
truss
ou strace
pour tracer
l'exécution.
-
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 ?
- 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 ?
- ajouter un autre verrou,
- 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.
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 :
-
Partir du canevas donné ici :
delay_until.c .
-
Utiliser l'horloge temps réel : clock_gettime(CLOCK_REALTIME, &time),
-
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.
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.
-
main :
- crée un verrou M1 dont la priorité est MAX-5
- crée ensuite une thread T0
- attend la fin de la thread T0
-
le thread T0:
- crée une thread T1
- fait un lock sur le verrou M1, donc éléve sa propre priorité à MAX-5.
- éléve la priorité de T1 à (MAX-10). T1 ne doit donc pas démarrer parce qu'on a maintenant : Prio(T0) > Prio(T1).
- effectue un calcul
- fait unlock sur le verrou M1
- attend la fin de la thread T1
- se termine
-
le thread T1 :
- effectue un calcul
- fait un trylock sur le verrou M1
- effectue un calcul
- fait unlock sur le verrou M1
- se termine
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.
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 :
-
Le thread T0 prend le verrou M1.
-
Le thread T1 fait d'abord sleep (4) puis démarre et exécute une très long
travail,
-
Le thread T2 fait d'abord sleep (2), démarre et essaie de prendre
le verrou M1.
Déroulement du scénario :
-
Le thread T0 démarre le premier et prend le verrou M1.
-
Le thread T2 démarre ensuite et se bloque sur le verrou M1,
déja pris par T0.
-
Le thread T1 démarre alors et ne s'arrête pas. Il empêche
T0, qui bloque T2 de rependre son exécution.
T2 le plus prioritaire ne peut plus travailler (inversion de priorité).
Questions :
-
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.
-
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 :
-
Le thread T0 démarre le premier,
prend le verrou M1et voit sa priorité AUGMENTEE au niveau de celle de T2.
-
Le thread T2 démarre ensuite et se bloque sur le verrou M1,
déja pris par T0.
-
Le thread T1 se réveille mais ne prend pas la main
parce que sa priorité est inférieure à celle
de T0.
-
le thread T0 progresse dans sa section critique, fait unlock,
perd sa priorité,
- donc le thread T2, le plus
prioritaire, reprend son exécution et entre dans sa section
critique,
RAPPEL CORRIGES:
essai00.c .
delay_until.c .