Theorie Threads Slides
Theorie Threads Slides
Claude VILVENS
[email protected]
- I216 -
(2015-2016)
Threads / Sommaire – Claude Vilvens – HEPL (Informatique)
Sommaire
Introduction
Sommaire - Page 1
Threads / Sommaire – Claude Vilvens – HEPL (Informatique)
Sommaire - Page 2
Threads / Sommaire – Claude Vilvens – HEPL (Informatique)
Ouvrages consultés
Sommaire - Page 3
Threads / Introduction – Claude Vilvens – HEPL (Informatique)
Introduction
Comme le montre cette métaphore, les threads (les Français disent "activités") sont
donc les amis des programmeurs modernes, puisqu'ils leur permettent de répartir les tâches
sur plusieurs unités de programmation exécutant leur code.
Bien sûr, les sous-processus classiques jouent déjà ce rôle. Cependant, les threads sont
plus simples d'utilisation, notamment parce qu'ils communiquent simplement et qu'ils sont
moins gourmands en ressources machine. En revenant à l'analogie de l'entreprise, les threads
sont les collaborateurs locaux, tandis que les sous-processus sont les correspondants éloignés.
On trouve l'usage des threads dans de nombreux contextes, comme la programmation
Windows ou Java. Pour ce qui nous concerne, nous ferons leur connaissance avec le
langage C, qui a le mérite de nous faire mieux ressentir "ce qui se passe à l'intérieur".
Avec toutes les précautions d'usage en programmation système, je vous invite donc à
me suivre dans cet univers impitoyable (celui qui crée des threads dans une boucle, au risque
de "planter" la machine, est en général massacré par ses congénères !) mais si séduisant …
Claude Vilvens
Page 1
Threads / Fonctions de base – Claude Vilvens – HEPL (Informatique)
Schématiquement :
process
registers
memory stack
heap
data
code
Page 2
Threads / Fonctions de base – Claude Vilvens – HEPL (Informatique)
process
registers registers registers
data
data data data
code
♦ le processus proprement dit, qui est l'unité d'encapsulation des ressources utilisées;
♦ les activités ou threads of control qui sont les unités d'exécution sur cet ensemble.
Puisqu'elles ne contiennent pas les données sur lesquelles elles agissent, ces unités
d'exécutions sont appelées des "processus légers" (lightweight process).
Page 3
Threads / Fonctions de base – Claude Vilvens – HEPL (Informatique)
heap
data
code
1) On sait qu'un tableur doit recalculer le feuille de calcul active à chaque fois qu'une cellule
est modifiée. Si la feuille est complexe, cela peut prendre un temps important, préjudiciable à
la souplesse d'utilisation du tableur – au point que l'on souhaiterait parfois inhiber la fonction
de recalcul automatique ! Une solution permettant d'alléger le travail de l'utilisateur consiste à
structurer l'application tableur en deux threads :
♦ un thread principal, qui gère le dialogue direct avec l'utilisateur, par exemple les saisies;
♦ un thread secondaire, de priorité moindre, qui s'occupe de recalculer le contenu des
cellules modifiées.
Page 4
Threads / Fonctions de base – Claude Vilvens – HEPL (Informatique)
Comme le thread de recalcul est moins prioritaire, il est préempté dès que l'utilisateur effectue
une saisie (thread principal actif, thread secondaire arrêté) alors qu'il peut s'exprimer lorsque
l'utilisateur n'encode rien (thread secondaire actif, thread principal arrêté).
4) Une tâche d'impression peut être confiée à un thread qui gérera cette opération de sortie
réputée pour sa lenteur pendant que le thread principal, plus prioritaire, permettra de
conserver l'usage de l'application.
5) La notion de thread est très utile dans la construction d'un serveur réseau multi-clients. En
effet, il suffira de créer un thread pour chaque requête d'un client, ce qui permettra
l'exécution en parallèle des traitements de ces requêtes. On conçoit sans peine, comme pour
les sous-processus, qu'un tel mécanisme est plus efficace que la méthode classique qui
consiste à placer les requêtes dans une file d'attente et à les traiter l'une après l'autre.
6) L'intérêt des threads est évident dans le cas de l'utilisation des systèmes multiprocesseurs
(of course !).
4. Différents modèles
Les threads peuvent accomplir leur tâche selon différentes modèles :
♦ le modèle parallèle ou du travail en équipe (work crew model or parallel model): après
une phase d'initialisation, les différents threads réalisent une sous-tâche de la tâche
commune; la nécessité de méthodes de synchronisation est évidente;
♦ le modèle pipeline (pipeline model) : les threads forment une chaîne et chaque thread
démarre lorsque le précédent a terminé, les résultats en sortie de l'un constituant les
données en entrée de l'autre;
C'est en général à ce dernier modèle que l'on pense lorsque l'on parle de serveur "multi-
threads".
Page 5
Threads / Fonctions de base – Claude Vilvens – HEPL (Informatique)
5. Du code réentrant
Il convient de remarquer que, dans le cas du modèle multi-threads parallèle ou dans
celui du producteur-consommateurs, plusieurs threads peuvent exécuter le même code
simultanément. Un environnement de développement multithreads doit fournir du code qui
supporte une telle exécution simultanée avec des points d'avancements différents : on parle
encore dans ce cas de code réentrant. Ceci signifie notamment que certaines fonctions des
librairies standards du C doivent être remplacées par leur version réentrante.
Pour le reste, un thread se comporte de manière assez semblable à un sous-processus.
Ainsi, il peut se trouver en différents états (en attente, prêt à être exécuté, en cours
d'exécution, terminé). Bien sûr, on rencontrera aussi les problèmes bien connus de la
synchronisation des diverses actions et des accès concurrents. Nous en reparlerons, car le
mécanisme des threads propose des solutions spécifiques à ces problèmes.
6. La norme POSIX
POSIX (Portable Operating System Interface) est une famille de standards développés
par IEEE (Institute for Electrical and Electronic Engineeers) et adoptés par l'ISO
(International Organization for Standardization). Le premier standard POSIX est apparu en
1988 (POSIX 1003.1) : il spécifiait les interfaces C pour un système UNIX.
Le mécanisme des threads, évidemment très bien adapté aux problèmes de temps réels
et à l'utilisation de machines multiprocesseurs, trouve sa norme POSIX d'interface dans
POSIX 1003.4a1, si bien que l'on parle des Posix threads ou P-threads. L'objectif est de
l'implémenter sur les systèmes ouverts. Les fonctions APIs correspondantes sont typiquement
"unixiennes" : 0 ou –1 comme valeur de retour, code d'erreur dans la variable globale errno.
En particulier, les threads POSIX sont implémentés les machines Unix
(essentiellement, au sein du Département informatique, les machines SunRay – il sera à
l'occasion encore fait référence aux anciennes machines Copernic et Boole). Le standard
respecté actuellement par ces machines est IEEE POSIX 1003.1c-1995 ou POSIX.1c. Elle
n'utilise pas la variable globale errno; les valeurs qu'aurait du prendre cette variable sont en
fait les valeurs de retour de la fonction correspondante. Cette façon de procéder s'explique
facilement : errno est en effet, en principe, une variable globale, donc partagée par les
différents threads et elle ne saurait donc être positionnée valablement par des threads
fonctionnant en parallèle ! En réalité, afin que chaque thread dispose de sa propre variable
errno, on a imaginé bien logiquement de la placer sur la pile de chaque thread, ce qui en
assure la duplication souhaitée. Pour qu'il en soit ainsi, on trouve dans errno.h:
#ifdef _REENTRANT
#define errno (*_errno())
#else
extern int errno;
#endif /* _REENTRANT */ ...
1
les standards POSIX sont constamment mis à jour : on peut consulter http://www.pasc.org/standing/sd11.html
(PASC pour Portable Applications Standards Committee).
Page 6
Threads / Fonctions de base – Claude Vilvens – HEPL (Informatique)
Dans la version réentrante, chaque thread possède donc bien une valeur locale de errno,
puisque donnée par la valeur de retour d'une fonction et pas par une variable externe !
Le format général des fonctions de threads POSIX est :
le suffixe np particularisant les fonctions non portables. Les noms de type utilisés par la
norme sont de la forme :
Remarques
1) On peut obtenir de l'aide sur les threads par (sous UNIX) man pthread et aussi (pour la
programmation sous Sun Solaris) sur http://docs.sun.com/app/docs/coll/40.10 .
2) Un thread peut posséder des données statiques qui lui sont propres: il existe en effet un
ensemble de données statiques réservé aux threads; ces données sont réparties à la demande
explicite entre les divers threads d'un processus. Nous en reparlerons.
Page 7
Threads / Fonctions de base – Claude Vilvens – HEPL (Informatique)
♦ créer un thread en lui indiquant la fonction qu'il doit exécuter (avec éventuellement son
paramètre) [pthread_create];
♦ terminer un thread en terminant la fonction qu'il exécute par un appel de pthread_exit qui
permet de renvoyer une valeur; ceci ne fait pas disparaître le thread !
♦ attendre qu'un thread se termine en récupérant sa valeur de retour [pthread_join];
♦ détruire définitivement un thread et ainsi récupérer ses ressources [pthread_detach]; le
thread en cours d'exécution n'est pas arrêté – il ne sera détruit que lorsqu'il aura terminé sa
tâche; on peut donc dire que cette fonction "marque pour la destruction" plutôt qu'elle ne
détruit réellement – en fait, elle "détache" le thread, c'est-à-dire qu'elle le rend
indépendant et laisse sa terminaison libérer ses ressources automatiquement.
pthread_create
paramètres
Le deuxième paramètre est du type pthread_attr_t : il s'agit d'une structure
permettant de fixer les caractéristiques du thread (priorité, taille initiale de la pile, le fait d'être
un démon ou pas, etc). Selon la stricte norme POSIX, les attributs par défaut sont définis par
la constante pthread_attr_default; pour les machines Sun et Compaq, NULL remplace cette
constante et a le même effet.
Les deux derniers paramètres désignent respectivement la fonction à exécuter par le
thread et le paramètre que l'on peut passer à cette fonction. On remarquera les pointeurs void*
qui n'attendent que les castings permettant d'agir selon la volonté du programmeur ... A priori,
la fonction exécutée par le thread a donc pour prototype :
void * f (void *)
pthread_create
erreurs
Page 8
Threads / Fonctions de base – Claude Vilvens – HEPL (Informatique)
pthread_exit
paramètres
Ceci permet au thread qui se termine de fournir une valeur de retour, passée en
paramètre et éventuellement testable par le thread père (voir pthread_join). On remarquera
que c'est l'adresse de la valeur de retour qui est renvoyée, et non cette valeur seulement.
Evidemment, s'il s'agit du thread principal, on a l'équivalent de exit(), si ce n'est que les
threads fils peuvent continuer.
Nous verrons qu'il est aussi possible d'interrompre un thread depuis l'extérieur de
celui-ci (pthread_cancel).
pthread_join
paramètres
Cette fonction récupère donc la valeur renvoyée par le thread en la plaçant dans une
zone mémoire référencée par une double indirection. En pratique, cela signifie simplement
que l'on peut renvoyer une donnée quelconque (void * en C).
2
à moins qu'il ait été victime d'un pthread_cancel
Page 9
Threads / Fonctions de base – Claude Vilvens – HEPL (Informatique)
pthread_join
erreurs
Si plusieurs threads tentent de "joindre" le même thread, le résultat est imprévisible – donc, à
éviter.
pthread_detach
erreurs
Une autre façon de dire la même chose est de considérer que cette fonction demande que le
thread soit détruit une fois son action terminée. A remarquer que la primitive pthread_join
détache également le thread une fois la synchronisation terminée.
Page 10
Threads / Fonctions de base – Claude Vilvens – HEPL (Informatique)
Schématiquement :
Thread
principal
create()
Thread
secondaire
main()
fct. thread
join()
THREAD01.C
/* THREAD01.C
Claude Vilvens
*/
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
void * fctThread(int * param);
pthread_t threadHandle;
int main()
{
int ret, * retThread;
int paramEff = 5;
puts("Thread principal demarre");
ret = pthread_create(&threadHandle, NULL, (void *(*) (void *))fctThread, ¶mEff);
puts("Thread secondaire lance !");
puts("Attente de la fin du thread secondaire");
ret = pthread_join(threadHandle, (void **)&retThread);
printf("Valeur renvoyee par le thread secondaire = %d\n", *retThread);
puts("Fin du thread principal");
}
Page 11
Threads / Fonctions de base – Claude Vilvens – HEPL (Informatique)
pthread_exit(&vr);
return 0;
}
On remarquera les castings pour éviter les warnings donnés par certains compilateurs C,
comme le compilateur C de Digital et le compilateur C++ de Digital.
♦ on fixe le niveau des librairies linkées au niveau réentrant (un must si on utilise des
threads) [-mt];
♦ on force forcer le link avec la librairie libpthread.so [-lpthread] :
sunray2v440.inpres.epl.prov-liege.be> thr
Thread principal demarre
Thread secondaire lance !
Attente de la fin du thread secondaire
th> Debut de thread
th> Parametre recu = 5
th> Valeur renvoyee par defaut = 1007
th> Nouvelle valeur : 654
th> Valeur renvoyee = 654
Valeur renvoyee par le thread secondaire = 654
Fin du thread principal
sunray2v440.inpres.epl.prov-liege.be>
g++ de nos jours :-)
En ce qui concerne C++, l'appel de CC (cxx sur certaines machines UNIX) remplace,
comme d'habitude, celui de cc, avec les mêmes options de compilation et de link.
Page 12
Threads / Fonctions de base – Claude Vilvens – HEPL (Informatique)
Remarques
1) Si on détache le thread sans attendre la fin de celui-ci :
THREAD01-D.C
/* THREAD01-D.C
Claude Vilvens
*/
…
int main()
{
int ret, paramEff = 5;
sunray2v440.inpres.epl.prov-liege.be> c
Thread principal demarre
Thread secondaire lance !
Detachement du thread
Fin du thread principal
sunray2v440.inpres.epl.prov-liege.be>
Autrement dit, le thread n'a pas le temps de parler, puisque son père s'est terminé !
2) La valeur de retour peut être récupérée dans une variable locale au lieu d'utiliser un
pointeur :
Page 13
Threads / Fonctions de base – Claude Vilvens – HEPL (Informatique)
THREAD01-R.C
/* THREAD01-R.C
Claude Vilvens
*/
…
int main()
{
int ret, retThread;
…
ret = pthread_create(&threadHandle, NULL,(void*(*)(void*)) fctThread, ¶mEff);
…
ret = pthread_join(threadHandle, (void **)&&retThread);
…
}
3) On peut remarquer que, pour bien faire, il faut éviter de renvoyer l'adresse d'une variable
locale. On peut donc penser à définir, dans la fonction exécutée par le thread, la variable de
retour comme statique : c'est ce qui est fait dans l'exemple ci-dessus. On peut aussi penser à
placer cette valeur de retour sur le heap. On peut ainsi, sans problème, imaginer ceci :
THREAD01-VR.C
/* THREAD01-VR.C
Claude Vilvens
*/
…
int main()
{ int *retThread;
…
ret = pthread_create(&threadHandle, NULL,(void*(*)(void*)) fctThread, ¶mEff);
puts("Thread secondaire lance !");
puts("Attente de la fin du thread secondaire");
ret = pthread_join(threadHandle, (void **)&retThread);
printf("Valeur renvoyee par le thread secondaire = %d\n", *retThread);
free(retThread);
puts("Fin du thread principal");
}
Page 14
Threads / Fonctions de base – Claude Vilvens – HEPL (Informatique)
pthread_exit(avr);
return 0;
}
Page 15
Threads / Fonctions complémentaires – Claude Vilvens – HEPL (Informatique)
soit par un label du type pid.tid. Ces deux éléments d'identification peuvent s'obtenir en
utilisant les fonctions :
La première est une vieille connaissance (pour rappel, on trouve dans types.h un typedef int
pid_t et le header unistd.h est nécessaire pour le prototype de getpid). La deuxième fonction
fournit le handle du thread qui l'a appelée.
pthread_self
erreurs
Il n'y a pas de code d'erreurs. Si la spécification du thread n'est pas valide, le résultat est
aléatoire.
int main()
Page 16
Threads / Fonctions complémentaires – Claude Vilvens – HEPL (Informatique)
THREAD01-NUM.C
/* THREAD01-NUM.C
Claude Vilvens
*/
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
void * fctThread(int * param);
pthread_t threadHandle;
Page 17
Threads / Fonctions complémentaires – Claude Vilvens – HEPL (Informatique)
int main()
{
int ret, * rethread, paramEff = 5;
unsigned long numSequence;
puts("Thread principal demarre");
printf("identite = %d.%u\n", getpid(), pthread_self());
numSequence = pthread_self( );
printf("Identite avec numero de sequence = %d.%u\n", getpid(), numSequence);
ret = pthread_create(&threadHandle, NULL,(void*(*)(void*)) fctThread, ¶mEff);
puts("Thread secondaire lance !");puts("Attente de la fin du thread secondaire");
ret = pthread_join(threadHandle, (void **)&retThread);
printf("Valeur renvoyee par le thread secondaire = %d\n", *retThread);
puts("Fin du thread principal");
}
Page 18
Threads / Fonctions complémentaires – Claude Vilvens – HEPL (Informatique)
nanosleep
paramètres
La structure de définition du temps est définie dans pthread.h par :
structure timespec
typedef struct timespec
{
unsigned long tv_sec; /* seconds */
long tv_nsec; /* nanoseconds */
} timespec_t;
nanosleep
retours
Page 19
Threads / Fonctions complémentaires – Claude Vilvens – HEPL (Informatique)
Thread
principal
create()
Thread
secondaire
main() (D)
fct. thread()
sleep() 5s
THREAD01-D-S.C
/* THREAD01-D-S.C
Claude Vilvens
*/
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
Page 20
Threads / Fonctions complémentaires – Claude Vilvens – HEPL (Informatique)
Page 21
Threads / Fonctions complémentaires – Claude Vilvens – HEPL (Informatique)
Remarque importante
Il va de soi que jouer ainsi sur les temps peut relever du bricolage programmatique :
dans les situations critiques, il faut impérativement utiliser les primitives de synchronisation
(comme join, ainsi que vu plus haut, ou wait, comme nous le verrons)
Thread create()
principal
main()
?s
?s
?s
20 s
Page 22
Threads / Fonctions complémentaires – Claude Vilvens – HEPL (Informatique)
SUPERMARCH01.C
/* SUPERMARCH01.C
Claude Vilvens
*/
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
pthread_t threadHandle;
int nbClientEntres = 0;
char ouvert=0;
assure la communication entre les threads
int main()
{
int ret, * retThread;
char buf[80], rep, nouveauClient;
/* Initialisation */
puts("Thread principal demarre");
affThread(getThreadIdentity(), "Thread principal demarre");
do
{
printf("Ouvrir le magasin ?");gets(buf);rep=buf[0];
}
while (rep!='O');
ouvert=1;
Page 23
Threads / Fonctions complémentaires – Claude Vilvens – HEPL (Informatique)
if (ouvert)
{
temps.tv_sec = rand()/5000;
temps.tv_nsec = 0;
sprintf(buf, "Attente de %d secondes ...", temps.tv_sec);
affThread(numThr, buf);
nanosleep(&temps, NULL);
}
else affThread(numThr, "Le magasin est fermé - trop tard :-( ...");
char * getThreadIdentity()
{
unsigned long numSequence;
char *buf = (char *)malloc(30);
numSequence = pthread_self( ) );
/* numSequence = pthread_getsequence_np( pthread_self( ) ); */
sprintf(buf, "%d.%u", getpid(), numSequence);
return buf;
}
Page 24
Threads / Fonctions complémentaires – Claude Vilvens – HEPL (Informatique)
Enfin, on peut voir que le générateur de nombres aléatoires n'est pas initialisé à chaque
exécution : on obtient donc toujours la même séquence, ce qui est pratique pour étudier les
variations de comportement en fonction de la valeur de certains paramètres. Si l'on désire un
véritable effet aléatoire, on utilisera srand().
sunray2v440.inpres.epl.prov-liege.be> c
Thread principal demarre
th_9238.1> Thread principal demarre
Ouvrir le magasin ?O
Nouveau client ! Bonjour !
Thread secondaire lance !
Detachement du thread
Nouveau client ! Bonjour !
Thread secondaire lance !
Detachement du thread
Nouveau client ! Bonjour !
Thread secondaire lance !
Detachement du thread
Nouveau client ! Bonjour !
Thread secondaire lance !
Detachement du thread
Nouveau client ! Bonjour !
Thread secondaire lance !
Detachement du thread
th_9238.4> Attente de 2 secondes ...
th_9238.5> Attente de 2 secondes ...
th_9238.6> Attente de 6 secondes ...
th_9238.7> Attente de 3 secondes ...
th_9238.8> Attente de 2 secondes ...
th_9238.4> Fin du thread client
th_9238.5> Fin du thread client
th_9238.8> Fin du thread client
th_9238.7> Fin du thread client
th_9238.6> Fin du thread client
Fin du thread principal
sunray2v440.inpres.epl.prov-liege.be>
Evidemment, il faudrait que nos clients achètent quelque chose. Cela va venir …
Page 25
Threads / Fonctions complémentaires – Claude Vilvens – HEPL (Informatique)
Remarque
Sur les machines Unix qui supportent les commandes utilisées, l'état des processus est,
pendant l'exécution :
sunray2v440.inpres.epl.prov-liege.be> ps -mlf
FS UID PID PPID %CPU PRI NI RSS WCHAN STARTED D
80c08001 I 28 8722 8719 0.0 44 0 256K pause 10:37:01 )
80808001 S + 28 9150 8722 0.0 44 0 448K * 11:37:34 c
S 0.0 44 0 nxmidle
S 0.0 44 0 nxmbloc
S 0.0 44 0 usleep
80c08001 S 28 8786 8783 0.0 44 0 256K pause 10:46:48 )
sunray2v440.inpres.epl.prov-liege.be>
Il n'y a pas de valeur de retour prévue … et pour cause, puisqu'il s'agit d'une macro !
L'appel de la fonction en fin d'exécution du thread n'est pas automatique (dans ce cas,
d'ailleurs, quel serait l'intérêt de pouvoir en définir plusieurs ?), il doit être provoqué par
l'appel de la fonction de dépilement :
tandis que la fonction à exécuter par le thread comportera les appels des macros :
Page 26
Threads / Fonctions complémentaires – Claude Vilvens – HEPL (Informatique)
pthread_cleanup_pop(1);
pthread_exit(&<valeur de fin>);
return 0;
}
La macro pthread_cleanup_pop() recevant un argument non nul, l'appel est exécuté juste
avant que le thread se termine.
SUPERMARCH02.C
/* SUPERMARCH02.C
Claude Vilvens
*/
…
int main()
{
int ret, * retThread;
char buf[80], rep, nouveauClient;
/* Initialisation */
…
/* Création des clients */
…
puts("Fin du thread principal");
}
Page 27
Threads / Fonctions complémentaires – Claude Vilvens – HEPL (Informatique)
pthread_cleanup_push(fctThreadClientFin,0);
if (ouvert)
{
temps.tv_sec = rand()/5000;
temps.tv_nsec = 0;
sprintf(buf, "Attente de %d secondes ...", temps.tv_sec);
affThread(numThr, buf);
nanosleep(&temps, NULL);
}
else affThread(numThr, "Le magasin est fermé - trop tard :-( ...");
sprintf(buf, "Fin du thread client");affThread(numThr, buf);
pthread_cleanup_pop(1);
pthread_exit(&vFin);
return 0;
}
char * getThreadIdentity() { … }
sunray2v440.inpres.epl.prov-liege.be> c
h_31453.1> Thread principal demarre
Ouvrir le magasin ?O
Nouveau client ! Bonjour !
Thread secondaire lance !
Detachement du thread
Nouveau client ! Bonjour !
Thread secondaire lance !
Detachement du thread
Nouveau client ! Bonjour !
Thread secondaire lance !
Detachement du thread
Nouveau client ! Bonjour !
Thread secondaire lance !
Detachement du thread
Nouveau client ! Bonjour !
Thread secondaire lance !
Detachement du thread
th_31453.2> Attente de 2 secondes ...
Page 28
Threads / Fonctions complémentaires – Claude Vilvens – HEPL (Informatique)
Bien sûr, tous les clients ont eu le temps de passer à la caisse à la condition que le
temps de sommeil du thread principal soit suffisamment long (par exemple, 20 secondes). Si
ce dernier est trop court, certains threads n'auront pas le temps d'exécuter leur fonction
terminale avant la fin du thread principal et donc de l'ensemble du processus.
Remarque
Sur les machines Compaq, le programme ci-dessus doit être généré avec une option –
lexc complémentaire :
Page 29
Threads / Asynchronisme – Claude Vilvens – HEPL (Informatique)
(Molière, Le Misanthrope)
♦ possède son propre masque de signaux, lequel est initialisé au moyen du masque du
thread créateur; libre ensuite au thread fils de modifier ce masque (par sigprocmask());
♦ peut installer un handler de traitement d'un signal particulier (par sigaction()), handler
utilisable par tous les autres threads.
Par conséquent, la délivrance synchrone d'un signal émis par le noyau qui vient de détecter
une exception dans l'exécution d'une instruction (par exemple, SIGINT ou SIGPIPE) se fera
au thread concerné. Par contre, et c'est logique, l'émission d'un signal asynchrone (par
exemple, un kill() à un processus d'identificateur précisé) est répercutée chez tous les threads
du processus – on ne sait donc pas quel thread exécute le handler.
Le petit exemple suivant nous donnera une idée des possibilités. Un processus, armé
pour traité SIGINT, lance deux threads. L'un d'entre eux est également armé pour traiter
SIGINT, l'autre, au contraire, bloque ce signal .
SIGNALTHREAD01.C
/* SIGNALTHREAD01.C
Claude Vilvens
*/
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
Page 30
Threads / Asynchronisme – Claude Vilvens – HEPL (Informatique)
int main()
{
int ret;
int paramEff1 = 1, paramEff2 = 2;
if (vr == 1)
{
#if 1
affThread(numThr, "Je capterai le signal SIGINT");
sigAct.sa_handler=handlerThreadSignal;
sigaction(SIGINT, &sigAct, NULL); réarme SIGINT
#endif
for(;;);
}
else
{
affThread(numThr, "Je masque le signal SIGINT");
sigemptyset(&mask);
sigaddset(&mask, SIGINT);
Page 31
Threads / Asynchronisme – Claude Vilvens – HEPL (Informatique)
Page 32
Threads / Asynchronisme – Claude Vilvens – HEPL (Informatique)
chaque thread) : le processus, bien que comportant plusieurs threads, reste unique ! Mais c'est
tantôt le thread principal, tantôt le thread secondaire qui capte le signal. Si on avait seulement
armé le thread principal sur SIGINT, on aurait obtenu le traitement par handlerSignal(),
toujours tantôt par main(), tantôt par le thread secondaire :
sunray2v440.inpres.epl.prov-liege.be> c
th_15868.1> Thread principal demarre
lancement du thread 1
Thread secondaire 1 lance !
lancement du thread 2
Thread secondaire 2 lance !
th_15868.2> . démarrage du thread
th_15868.2> Parametre recu = 1
th_15868.3> . démarrage du thread
th_15868.3> Parametre recu = 2
th_15868.3> Je masque le signal SIGINT
th_15868.1> handlerSignal pour main a recu SIGINT
th_15868.1> handlerSignal pour main a recu SIGINT
th_15868.1> handlerSignal pour main a recu SIGINT
th_15868.1> handlerSignal pour main a recu SIGINT
th_15868.1> handlerSignal pour main a recu SIGINT
th_15868.1> handlerSignal pour main a recu SIGINT
th_15868.2> handlerSignal pour main a recu SIGINT
th_15868.1> handlerSignal pour main a recu SIGINT
th_15868.2> handlerSignal pour main a recu SIGINT
th_15868.1> handlerSignal pour main a recu SIGINT
Killed
sunray2v440.inpres.epl.prov-liege.be>
Et si l'on avait seulement armé le thread secondaire sur SIGINT, les choses auraient été
semblables, mais avec le handler du thread. Tout ceci pour bien rappeler que les threads font
partie du même processus …
Remarque
Deux primitives particulières aux threads ont été définies pour ce qui concerne les signaux :
♦ int pthread_sigmask (<type de modification pour l'ensemble des signaux masqués – int>
<masque à définir - const sigset_t * >,
<valeur actuelle du masque - sigset_t * >);
- comme sa cousine sigprocmask(), cette primitive ne peut bloquer les signaux SIGKILL et
SIGSTOP.
Page 33
Threads / Asynchronisme – Claude Vilvens – HEPL (Informatique)
Thread
principal
armement
etc
client 1 client 2 client 5
(D) (D) (D)
SIGALRM
S 10 s
?s
?s
?s
signal
captage
SUPERMARCH03.C
/* SUPERMARCH03.C
Claude Vilvens
*/
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
Page 34
Threads / Asynchronisme – Claude Vilvens – HEPL (Informatique)
pthread_t threadHandle;
struct sigaction sigAct;
int nbClientEntres = 0;
char ouvert=0;
int vFin = 99;
int main()
{
int ret, * retThread;
char buf[80], rep, nouveauClient;
/* Initialisation */
puts("Thread principal demarre");
affThread(getThreadIdentity(), "Thread principal demarre");
do
{
printf("Ouvrir le magasin ?");gets(buf);rep=buf[0];
}
while (rep!='O');
ouvert=1;
sigAct.sa_handler = handlerSignalAlarme;
sigaction(SIGALRM, &sigAct, NULL); armement de SIGALRM
alarm(10);
alarm n'est pas bloquant !
/* Création des clients */
while (ouvert && nbClientEntres<=5)
{
nouveauClient = rand()%5 ==0 ;
if (nouveauClient)
{
puts("Nouveau client ! Bonjour !");
ret = pthread_create(&threadHandle, NULL,
(void *(*) (void *))fctThreadClient,0);
puts("Thread secondaire lance !");nbClientEntres++;
puts("Detachement du thread");
ret = pthread_detach(threadHandle);
}
}
while(1);
}
Page 35
Threads / Asynchronisme – Claude Vilvens – HEPL (Informatique)
pthread_cleanup_push(fctThreadClientFin,0);
if (ouvert)
{
temps.tv_sec = rand()/5000; temps.tv_nsec = 0;
sprintf(buf, "Attente de %d secondes ...", temps.tv_sec);affThread(numThr, buf);
nanosleep(&temps, NULL);
}
else affThread(numThr, "Le magasin est fermé - trop tard :-( ...");
sprintf(buf, "Fin du thread client");affThread(numThr, buf);
pthread_cleanup_pop(1);
pthread_exit(&vFin);
return 0;
}
char * getThreadIdentity() {… }
sunray2v440.inpres.epl.prov-liege.be> c
Thread principal demarre
th_27235.1> Thread principal demarre
Ouvrir le magasin ?O
Nouveau client ! Bonjour !
Thread secondaire lance !
Detachement du thread
Nouveau client ! Bonjour !
Thread secondaire lance !
Detachement du thread
Nouveau client ! Bonjour !
Thread secondaire lance !
Detachement du thread
th_27235.4> Attente de 6 secondes ...
Page 36
Threads / Asynchronisme – Claude Vilvens – HEPL (Informatique)
♦ le temps d'ouverture imparti est trop court pour permettre la création des 5 clients
(maximum prévu);
♦ les fonctions de terminaisons sont appelées pour tous les threads créés.
int pthread_cancel (<handle du thread - pthread_t>); permet à thread de "tuer" un autre thread
dont la fonction est bien claire : envoyer une demande d'arrêt au thread concerné. Il est donc
demandé à celui-ci de se terminer "aussi vite que possible", notion qui varie selon sa
configuration (voir ci-dessous). En fait, envoyer une requête de terminaison à un thread ne
garantit pas qu'il va obtempérer, ni même recevoir le demande !
pthread_cancel
erreurs
Comme on l'a dit, la manière de prendre en compte une telle demande dépend de la
manière dont le thread a été configuré vis-à-vis de ce type d'événement. Cette configuration
est déterminée par deux indicateurs positionnés par deux fonctions.
1) La première positionne un indicateur général qui, lorsqu'il est inhibé, reporte la prise en
compte des requêtes reçues au moment où cet indicateur est à nouveau activé. Par défaut, cet
indicateur est positionné. La syntaxe de la fonction est, sous Digital UNIX :
#define PTHREAD_CANCEL_DISABLE 0
#define PTHREAD_CANCEL_ENABLE 1
pthread_setcancelstate
erreurs
Remarque
La fonction équivalente de la norme POSIX 1003.4a est :
2) La deuxième fonction, qui ne présente d'intérêt que si l'indicateur général est positionné,
positionne un deuxième indicateur d'asynchronisme. Sous Digital UNIX :
Si il est activé, une requête d'abandon est prise en compte immédiatement. Sinon, la demande
sera pris en compte au premier point de contrôle rencontré, lequel est mis en place par la
fonction :
plus tard, cad au moment de l'appel de
void pthread_testcancel (void);
La demande est, de toute manière, prise en compte lors des appels de fonctions de
synchronisation comme pthread_join() ou pthread_cond_wait()3.
pthread_setcanceltype
erreurs
3
voir plus loin (synchronisation des threads)
Page 38
Threads / Asynchronisme – Claude Vilvens – HEPL (Informatique)
Remarque
A nouveau, il existe une fonction équivalente à setcanceltype( )pour la norme POSIX 1003.4a
est :
4. Le supermarché : il y a un voleur
Imaginons qu'un client du supermarché soit en fait un voleur. Lorsque l'on s'en rend
compte, le gestionnaire du magasin jette le voleur dehors – après l'avoir fait passer à la caisse
(et encore, c'est parce qu'il est bon !).
Dans la simulation, lorsqu'un thread est détecté comme un voleur, on lance un signal
SIGUSR1 au thread principal, qui va arrêter les sombres agissements du sinistre personnage.
Page 39
Threads / Asynchronisme – Claude Vilvens – HEPL (Informatique)
Thread
principal
M
SIGALR
SIGUSR1
?
S 10 s
client 1 client 2 client 5
voleur
(D) (D) (D)
cpt
etc
?s ?s ?s
cancel()
ou en version "compactée"
pour les threads semblables
(le symbole "*" signifie Thread
"existe en plusieurs principal
occurrences") :
RM
SIGAL
S 10 s
SIGUSR1
?
client *
voleur
(D)
cpt
?s
cancel()
Page 40
Threads / Asynchronisme – Claude Vilvens – HEPL (Informatique)
SUPERMARCH04.C
/* SUPERMARCH04.C
Claude Vilvens
*/
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
pthread_t threadHandle;
pthread_t threadHandleVoleur;
struct sigaction sigAct;
int nbClientEntres = 0;
char ouvert=0;
int vFin = 99;
int main()
{
int ret, * retThread, aleat;
char buf[80], rep, nouveauClient;
/* Initialisation */
puts("Thread principal demarre");
affThread(getThreadIdentity(), "Thread principal demarre");
do
{
printf("Ouvrir le magasin ?");gets(buf);rep=buf[0];
}
while (rep!='O');
ouvert=1;
sigAct.sa_handler = handlerSignalAlarme;
sigaction(SIGALRM, &sigAct, NULL);
sigemptyset(&sigAct.sa_mask);
sigAct.sa_handler = handlerVoleur;
if ( (ret=sigaction(SIGUSR1, &sigAct, 0)) == -1)
perror("\nErreur de sigaction sur SIGUSR1");
alarm(25);
Page 41
Threads / Asynchronisme – Claude Vilvens – HEPL (Informatique)
pthread_cleanup_push(fctThreadClientFin,0);
if (pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, &ancEtat))
puts("Erreur de setcancelstate");
if (pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, &ancEtat))
puts("Erreur de setcanceltype");
#if 0
pthread_testcancel();
#endif
if (ouvert)
{
temps.tv_sec = rand()/5000;
temps.tv_nsec = 0;
sprintf(buf, "Attente de %d secondes ...", temps.tv_sec);
Page 42
Threads / Asynchronisme – Claude Vilvens – HEPL (Informatique)
affThread(numThr, buf);
nanosleep(&temps, NULL);
}
else affThread(numThr, "Le magasin est fermé - trop tard :-( ...");
sprintf(buf, "Fin du thread client");affThread(numThr, buf);
pthread_cleanup_pop(1);
pthread_exit(&vFin);
return 0;
}
Une première exécution sur une machine Unix relativement lente (ici, Boole), sans
l'appel de test_cancel() donne (attention au décalage entre le numéro de client et le numéro de
séquence du thread associé) :
Page 43
Threads / Asynchronisme – Claude Vilvens – HEPL (Informatique)
Sur une machine Unix relativement plus rapide (ici, Copernic), toujours sans
testcancel() :
copernic.inpres.epl.prov-liege.be> c
Thread principal demarre
th_13759.1> Thread principal demarre
Ouvrir le magasin ?O
1. Nouveau client ! Bonjour !Thread secondaire lance !
Detachement du thread
2. Nouveau client ! Bonjour !
th_13759.2> Attente de 1 secondes ...
th_13759.3> Attente de 3 secondes ...
Thread secondaire lance !
Detachement du thread
3. Nouveau client ! Bonjour !Thread secondaire lance !
th_13759.4> Attente de 6 secondes ...
!!!!!!!!!!! Au voleur !!!!!!!!!!!!
4. Nouveau client ! Bonjour !Thread secondaire lance !
Detachement du thread
5. Nouveau client ! Bonjour !Thread secondaire lance !
Detachement du thread
th_13759.4> ... je passe à la caisse ... même pas eu le temps d'entrer :-)
th_13759.5> Attente de 3 secondes ... qu'il doit passer à la caisse
th_13759.6> Attente de 5 secondes ...
th_13759.2> Fin du thread client
th_13759.2> ... je passe à la caisse ...
th_13759.3> Fin du thread client
th_13759.3> ... je passe à la caisse ...
th_13759.5> Fin du thread client
th_13759.5> ... je passe à la caisse ...
th_13759.6> Fin du thread client
th_13759.6> ... je passe à la caisse ...
!-!-! Notre magasin ferme ses portes !-!-!
copernic.inpres.epl.prov-liege.be>
Dans les deux cas, on peut constater que le thread voleur (n° 4) n'a pas le temps de se
terminer normalement : on l'envoie immédiatement à la caisse puisque, interrompu, il exécute
immédiatement sa fonction de terminaison.
Sur copernic, le thread voleur n'a même pas eu le temps de se mettre en attente. On
peut forcer cela dans tous les cas si nous reprenons l'exemple sur Boole avec un test_cancel() :
Page 44
Threads / Asynchronisme – Claude Vilvens – HEPL (Informatique)
boole.inpres.epl.prov-liege.be> c
Thread principal demarre
th_22770.1> Thread principal demarre
Ouvrir le magasin ?O
1. Nouveau client ! Bonjour !Thread secondaire lance !
Detachement du thread
2. Nouveau client ! Bonjour !Thread secondaire lance !
Detachement du thread
3. Nouveau client ! Bonjour !Thread secondaire lance !
!!!!!!!!!!! Au voleur !!!!!!!!!!!!
4. Nouveau client ! Bonjour !Thread secondaire lance !
Detachement du thread
5. Nouveau client ! Bonjour !Thread secondaire lance !
Detachement du thread
th_22770.2> Attente de 2 secondes ...
th_22770.3> Attente de 2 secondes ...
th_22770.4> ... je passe à la caisse ...
th_22770.5> Attente de 6 secondes ...
th_22770.6> Attente de 3 secondes ...
th_22770.2> Fin du thread client
th_22770.2> ... je passe à la caisse ...
th_22770.3> Fin du thread client
th_22770.3> ... je passe à la caisse ...
th_22770.6> Fin du thread client
th_22770.6> ... je passe à la caisse ...
th_22770.5> Fin du thread client
th_22770.5> ... je passe à la caisse ...
!-!-! Notre magasin ferme ses portes !-!-!
boole.inpres.epl.prov-liege.be>
struct article
{
représente un article du stock du magasin
int num;
char libelle[30];
double prixUnitaire;
int quStock;
};
Bien sûr, le stock devrait être un fichier. Mais, pour alléger et simplifier, mettons que ce stock
sera simplement représenté dans un tableau mémoire. Chaque client va donc se promener
dans le magasin et acheter un nombre aléatoire d'articles. Pour l'instant, ils seront tous
différents et on n'achètera qu'un seul article de type donné. Un petit tableau local contiendra
les numéros des articles déjà achetés. On supposera aussi que le stock est suffisant. Toutes ces
hypothèses simplificatrices seront levées au chapitre suivant.
Page 45
Threads / Asynchronisme – Claude Vilvens – HEPL (Informatique)
Avec de telles activités, nos threads clients peuvent passer un temps plus long dans le
magasin. Par conséquent, il se peut très bien que lorsque l'heure de la fermeture sonne certains
clients soient encore occupés … Dans un premier temps (mais nous ferons mieux après),
l'ensemble du processus sera arrêté violemment (c'est-à-dire par un SIGKILL), après un
ultime délai pour éventuellement terminer les achats.
Notre programme évoluera donc de la manière suivante (le chocolat est avarié et ne
peut être vendu ;-) ) :
SUPERMARCHE05.C
/* SUPERMARCHE05.C
Claude Vilvens */
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#define affThread(num, msg) printf("th_%s> %s\n", num, msg)
#define N_MAX_CLIENTS 6
#define random(n) (rand()%(n)+1) nombre aléatoire compris entre 1 et n
void * fctThreadClient(int * param);
void fctThreadClientFin (void *p);
void handlerSignalAlarme(int sig);
void handlerVoleur (int sig);
char * getThreadIdentity();
char exists(int n, int tab[], int dim); vérifie si n se trouve déjà dans tab[]
pthread_t threadHandle;
pthread_t threadHandleVoleur;
struct sigaction sigAct;
/* ---------------------------- */
/* Pour les articles du magasin */
struct article
{
int num;
char libelle[30];
double prixUnitaire;
int quStock;
};
#define NB_ART_DIFF 6
struct article magasins[] =
{
{1, "chocolat", 39, 2300},
{2, "whisky", 599, 50},
{3, "cereales", 67, 2600}, stock du magasin
{4, "chips", 20, 5000},
{5, "preservatif", 20, 4000},
{6, "herbes aromatiques", 67, 3450}
};
Page 46
Threads / Asynchronisme – Claude Vilvens – HEPL (Informatique)
/* ---------------------------- */
int nbClientEntres = 0;
char ouvert=0;
int main()
{
int ret, * retThread;
char buf[80], rep, nouveauClient;
/* Initialisation */
affThread(getThreadIdentity(), "Thread principal demarre");
do
{
printf("Ouvrir le magasin ?");gets(buf);rep=buf[0];
}
while (rep!='O');
ouvert=1;
sigAct.sa_handler = handlerSignalAlarme;
sigaction(SIGALRM, &sigAct, NULL);
sigemptyset(&sigAct.sa_mask);
sigAct.sa_handler = handlerVoleur;
if ( (ret=sigaction(SIGUSR1, &sigAct, 0)) == -1)
perror("\nErreur de sigaction sur SIGUSR1");
alarm(25);
Page 47
Threads / Asynchronisme – Claude Vilvens – HEPL (Informatique)
pthread_cleanup_push(fctThreadClientFin,0);
if (pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, &ancEtat))
puts("Erreur de setcancelstate");
if (pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, &ancEtat))
puts("Erreur de setcanceltype");
pthread_testcancel();
if (ouvert)
{
do
{
nbArt = random(NB_ART_DIFF); nombre d'articles différents qu'il va
} acheter
while (nbArt<=0);
sprintf(buf,"... je compte acheter %d articles", nbArt);
affThread(numThr, buf);
fini=0;
do
{
int na = random(NB_ART_DIFF-1); no de l'article acheté (impossible d'
if (!exists(na, numAch, NB_ART_DIFF)) acheter le chocolat)
{ on ne l'a pas encore celui-là
numAch[i]=na;
i++;
sprintf(buf, "J'achete 1 %s", magasins[na].libelle);
affThread(numThr, buf);
}
nEssai++;
if (nEssai%20==0) sched_yield(); au bout de 20 essais, je lâche le processeur
/* Sinon la recherche peut durer ... */
fini = i==nbArt || ouvert==0;
}
while (!fini);
sprintf(buf, "J'ai fini mon tour"); affThread(numThr, buf);
temps.tv_sec = rand()/5000;
Page 48
Threads / Asynchronisme – Claude Vilvens – HEPL (Informatique)
temps.tv_nsec = 0;
sprintf(buf, "Attente a la caisse de %d secondes ...",
temps.tv_sec);affThread(numThr, buf);
nanosleep(&temps, NULL);
}
else affThread(numThr, "Le magasin est ferme - trop tard :-(");
pthread_cleanup_pop(0);
return 0;
}
numSequence = threadHandleVoleur;
puts(" !!!!!!!!!!! Au voleur !!!!!!!!!!!!");
sprintf(buf, "!-! Le thread : %u : Dehors !!!", numSequence);
affThread(numThr, buf);
if ( ret=pthread_cancel(threadHandleVoleur) )
affThread(getThreadIdentity(), "erreur de pthread_cancel");
}
Page 49
Threads / Asynchronisme – Claude Vilvens – HEPL (Informatique)
Pour que la simulation prenne en compte les cas limites, on a sciemment programmé la
possibilité de vouloir choisir 6 articles différents, alors qu'en fait seulement 5 sont disponibles
(on ne vend pas de chocolat avarié). De plus, le temps d'ouverture a été choisi suffisamment
court pour qu'il se puisse très bien qu'un client soit encore occupé au moment de la fermeture,
même s'il n'achète que 5 articles différents au plus. Le thread client qui correspond, par
malheur, à l'un de ces cas risque donc de ne pas se terminer de sitôt … à moins qu'il ne teste
périodiquement la variable globale ouvert qui lui apprendra que le magasin ferme ! Pour
assurer que les choses se passent bien ainsi, nous allons obliger un tel thread à périodiquement
passer la main.
void sched_yield(void);
Le résultat est qu'un thread de priorité supérieure ou égale recevra alors la main. Bien sûr, si
un tel thread n'existe pas, le thread initial conserve la main et poursuit son exécution. En
pratique, on utilise cette fonction pour tenter d'optimaliser les performances. Dans notre
exemple, c'est essentiellement pour laisser le thread principal mettre la variable ouvert à 0.
Sur une machine lente comme boole, on obtient une exécution du type :
sunray2v440.inpres.epl.prov-liege.be> c
th_30589.1> Thread principal demarre
Ouvrir le magasin ?O
0. Nouveau client ! Bonjour !Thread secondaire lance !
Detachement du thread
1. Nouveau client ! Bonjour !Thread secondaire lance !
Detachement du thread
2. Nouveau client ! Bonjour !Thread secondaire lance !
!!!!!!!!!!! Au voleur !!!!!!!!!!!!
th_30589.1> !-! Le thread : 4 : Dehors !!!
3. Nouveau client ! Bonjour !Thread secondaire lance !
Detachement du thread
4. Nouveau client ! Bonjour !Thread secondaire lance !
Detachement du thread
5. Nouveau client ! Bonjour !Thread secondaire lance !
Page 50
Threads / Asynchronisme – Claude Vilvens – HEPL (Informatique)
Detachement du thread
== C'était le dernier client ==
th_30589.2> ... je compte acheter 5 articles
th_30589.2> J'achete 1 cereales
th_30589.2> J'achete 1 herbes aromatiques le thread est préempté
th_30589.2> J'achete 1 chips – mais il faut encore
th_30589.2> J'achete 1 preservatif acheter un article !
th_30589.3> ... je compte acheter 1 articles
th_30589.3> J'achete 1 preservatif
th_30589.3> J'ai fini mon tour
th_30589.3> Attente a la caisse de 0 secondes ...
th_30589.4> ... je passe à la caisse ...
th_30589.5> ... je compte acheter 5 articles
th_30589.5> J'achete 1 preservatif
th_30589.5> J'achete 1 chips
th_30589.5> J'achete 1 whisky
th_30589.5> J'achete 1 herbes aromatiques
th_30589.5> J'achete 1 cereales
th_30589.5> J'ai fini mon tour
th_30589.5> Attente a la caisse de 0 secondes ...
th_30589.6> ... je compte acheter 5 articles
th_30589.6> J'achete 1 herbes aromatiques
th_30589.6> J'achete 1 preservatif
th_30589.6> J'achete 1 chips
th_30589.6> J'achete 1 cereales
th_30589.6> J'achete 1 whisky
th_30589.6> J'ai fini mon tour
th_30589.6> Attente a la caisse de 2 secondes ...
th_30589.7> ... je compte acheter 1 articles
th_30589.7> J'achete 1 herbes aromatiques
th_30589.7> J'ai fini mon tour
th_30589.7> Attente a la caisse de 1 secondes ...
th_30589.3> Fin du thread client - il va sortir
th_30589.3> ... je passe à la caisse ...
th_30589.5> Fin du thread client - il va sortir
th_30589.5> ... je passe à la caisse ...
th_30589.7> Fin du thread client - il va sortir
th_30589.7> ... je passe à la caisse ...
th_30589.6> Fin du thread client - il va sortir
th_30589.6> ... je passe à la caisse ...
!-!-! Notre magasin ferme ses portes !-!-!
th_30589.1> !-! Nous vous laissons 10 secondes ...
th_30589.2> J'ai fini mon tour bien obligé !
th_30589.2> Attente a la caisse de 6 secondes ...
th_30589.2> Fin du thread client - il va sortir
th_30589.2> ... je passe à la caisse ...
Killed
sunray2v440.inpres.epl.prov-liege.be>
Page 51
Threads / Asynchronisme – Claude Vilvens – HEPL (Informatique)
copernic.inpres.epl.prov-liege.be> c
th_27034.1> Thread principal demarre
Ouvrir le magasin ?O
0. Nouveau client ! Bonjour !Thread secondaire lance !
Detachement du thread
1. Nouveau client ! Bonjour !th_27034.2> ... je compte acheter 4 articles
th_27034.2> J'achete 1 cereales
th_27034.2> J'achete 1 herbes aromatiques
th_27034.2> J'achete 1 chips
th_27034.2> J'achete 1 whisky
th_27034.2> J'ai fini mon tour
th_27034.2> Attente a la caisse de 6 secondes ...
th_27034.3> ... je compte acheter 1 articles
th_27034.3> J'achete 1 preservatif
th_27034.3> J'ai fini mon tour
th_27034.3> Attente a la caisse de 5 secondes ...
Thread secondaire lance !
Detachement du thread
2. Nouveau client ! Bonjour !Thread secondaire lance !
Detachement du thread
3. Nouveau client ! Bonjour !th_27034.4> ... je compte acheter 4 articles
Thread secondaire lance !
th_27034.5> ... je compte acheter 3 articles
th_27034.5> J'achete 1 herbes aromatiques
th_27034.5> J'achete 1 whisky
th_27034.5> J'achete 1 chips
th_27034.5> J'ai fini mon tour
th_27034.5> Attente a la caisse de 4 secondes ...
th_27034.4> J'achete 1 chips
th_27034.4> J'achete 1 herbes aromatiques
th_27034.4> J'achete 1 preservatif
th_27034.4> J'achete 1 cereales
th_27034.4> J'ai fini mon tour
Detachement du thread
4. Nouveau client ! Bonjour !th_27034.4> Attente a la caisse de 5 secondes ...
th_27034.6> ... je compte acheter 4 articles
th_27034.6> J'achete 1 chips
th_27034.6> J'achete 1 cereales
th_27034.6> J'achete 1 whisky
th_27034.6> J'achete 1 preservatif
th_27034.6> J'ai fini mon tour
th_27034.6> Attente a la caisse de 5 secondes ...
Thread secondaire lance !
Detachement du thread
5. Nouveau client ! Bonjour !Thread secondaire lance !
th_27034.7> ... je compte acheter 5 articles
!!!!!!!!!!! Au voleur !!!!!!!!!!!!
th_27034.1> !-! Le thread : 7 : Dehors !!!
Page 52
Threads / Asynchronisme – Claude Vilvens – HEPL (Informatique)
Ici, l'exécution a été très rapide, plus chaotique, et aucun client n'a vu trop grand, si bien que
tous les clients ont terminé leurs achats avant la fermeture. Ce qui nous indique que :
Page 53
Threads / Synchronisation – Claude Vilvens – HEPL (Informatique)
Page 54
Threads / Synchronisation – Claude Vilvens – HEPL (Informatique)
accès BD
Thread
principal
stock
etc
?
S R1
voleur gérant client 1 client 2 client 5
SIGU
S 10 s
SIGALRM
fini
fini
fini
?s
?s
?s
Page 55
Threads / Synchronisation – Claude Vilvens – HEPL (Informatique)
SUPERMARCHE06.C
/* SUPERMARCHE06.C
Claude Vilvens
*/
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sched.h>
pthread_t threadHandle;
pthread_t threadHandleVoleur;
pthread_t threadHandleGerant;
struct sigaction sigAct;
/* ---------------------------- */
/* Pour les articles du magasin */
…
/* ---------------------------- */
int nbClientEntres = 0;
char ouvert=0;
int *retThread;
int main()
{
int ret;
char * buf = (char *)malloc(80), rep, nouveauClient;
/* Initialisation */
…
sigAct.sa_handler = handlerVoleur;
if ( (ret=sigaction(SIGUSR1, &sigAct, 0)) == -1)
perror("\nErreur de sigaction sur SIGUSR1");
Page 56
Threads / Synchronisation – Claude Vilvens – HEPL (Informatique)
pthread_cleanup_push(fctThreadClientFin,0);
if (pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, &ancEtat))
puts("Erreur de setcancelstate");
if (pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, &ancEtat))
puts("Erreur de setcanceltype");
pthread_testcancel();
if (ouvert)
{
…
if (ouvert) sprintf(buf, "J'ai fini mon tour");
else sprintf(buf,"Tant pis ! Je n'ai plus le temps");
affTrhread(numThr, buf);
…
}
else affThread(numThr,"Le magasin est ferme - trop tard :-(");
Page 57
Threads / Synchronisation – Claude Vilvens – HEPL (Informatique)
pthread_cleanup_pop(1);
pthread_exit(0);
return 0;
}
temps.tv_sec = 10;
Page 58
Threads / Synchronisation – Claude Vilvens – HEPL (Informatique)
temps.tv_nsec = 0;
sprintf(buf, "!-! Nous vous laissons %d secondes ...", temps.tv_sec);
affThread(numThr, buf);
nanosleep(&temps, NULL);
Page 59
Threads / Synchronisation – Claude Vilvens – HEPL (Informatique)
Il convient de bien remarquer que les threads clients ont bloqué le signal SIGALRM. S'il n'en
était pas ainsi, l'un d'entre eux pourrait exécuter le handler handlerSignalAlarme(), ne pas
exécuter sa fonction de terminaison tandis que le gérant attendrait la fin du client : ce qui
conduirait à un blocage …
Page 60
Threads / Synchronisation – Claude Vilvens – HEPL (Informatique)
1) Ceci peut se faire en définissant un attribut pour le thread qui spécifiera cette priorité. On
utilise pour cela la fonction
pthread_attr_setschedparam
paramètres
Le deuxième paramètre est une structure, définie dans pthread.h et qui a pour rôle de
définir les propriétés d'un thread, dont la priorité. Dans la version la plus simple :
et il suffit donc d'affecter la valeur souhaitée au champ sched_priority. Les niveaux de priorité
minimal et maximal sont spécifié par des constantes définies dans pthread.h :
pthread_ attr_setschedparam
erreurs
Page 61
Threads / Synchronisation – Claude Vilvens – HEPL (Informatique)
2) Mais pour définir cette priorité, il faut d'abord définir une politique de scheduling (encore,
bien sûr, qu'il existe une politique par défaut). Cette politique se définit en construisant un
attribut de thread par :
pthread_attr_setschedpolicy
paramètres
Le deuxième paramètre est un entier qui peut prendre les valeurs symboliques définies
dans pthread.h :
#ifndef _PTHREAD_ENV_UNIX
# define SCHED_FIFO 1 /* Digital UNIX sched.h defines */
# define SCHED_RR 2 /* these constants already */
# define SCHED_OTHER 3
# define SCHED_FG_NP SCHED_OTHER /* "Foreground" (Timeshare) */
# define SCHED_BG_NP (SCHED_OTHER+1) /* "Background" */
# define SCHED_LFI_NP (SCHED_OTHER+2) /* "Low FIFO" (background FIFO) */
# define SCHED_LRR_NP (SCHED_OTHER+3) /* "Low RR" (background RR) */
#endif
♦ SCHED_FIFO
Le thread ayant la plus haute priorité reçoit la main jusqu'à ce qu'il soit bloqué. Un thread du
groupe des threads les plus prioritaires est alors activé jusqu'à ce qu'il se bloque à son tour. Si
un thread avec une priorité supérieure devient prêt à être exécuté, il préempte le thread actif et
prend la main.
♦ SCHED_RR (Round_Robin)
Le thread ayant la plus haute priorité reçoit la main jusqu'à ce qu'il soit bloqué mais les autres
threads ayant cette même haute priorité reçoivent une part de temps processeurs (time-slice).
Si un thread avec une priorité supérieure devient prêt à être exécuté, il préempte le thread actif
et prend la main.
♦ SCHED_FG_NP (ou SCHED_OTHER),
C'est la valeur par défaut. Tous les threads, quelle que soit leur priorité, reçoivent une part de
temps. Mais les threads de plus haute priorité reçoivent une plus grande part de temps que les
autres. Aucun thread ne se voit donc refuser l'exécution alors que c'est possible avec les deux
politiques précédentes.
♦ SCHED_BG_NP (Background)
Elle est analogue à la politique par défaut, mais les threads en tâche de fond reçoivent moins
de temps.
Page 62
Threads / Synchronisation – Claude Vilvens – HEPL (Informatique)
pthread_attr_setschedpolicy
erreurs
3) Selon la politique retenue, on dispose alors des symboles définissant les niveaux extrêmes
de priorité. Citons :
4) Pour définir la politique de scheduling, il faut encore au préalable renoncer à celle qui a été
donnée par défaut au thread au moment de sa création; en effet, un thread hérite à priori de la
politique du thread qui le crée. Pour qu'il n'en soit pas ainsi, et permettre ainsi une définition
propre, il faut appeler la primitive :
pthread_attr_ setinheritsched
paramètres
Le deuxième paramètre est un entier qui peut prendre les valeurs symboliques définies
dans pthread.h :
#define PTHREAD_INHERIT_SCHED 0
#define PTHREAD_EXPLICIT_SCHED 1
C'est évidemment la deuxième valeur qu'il faut spécifier pour préciser que le thread n'hérite
pas de la politique de son créateur.
5) En résumé, si l'on désire confectionner un attribut de thread avec une priorité maximale
pour un scheduling de type Round-Robin, on programmera :
Page 63
Threads / Synchronisation – Claude Vilvens – HEPL (Informatique)
pthread_attr_t attrThrPrincipal;
struct sched_param schedP;
pthread_attr_setinheritsched(&attrThrPrincipal, PTHREAD_EXPLICIT_SCHED);
pthread_attr_setschedpolicy(&attrThrPrincipal, SCHED_RR);
schedP.sched_priority = PRI_RR_MAX;
pthread_attr_setschedparam(&attrThrPrincipal, &schedP);
Remarque
Insistons bien sur le fait que :
structure pthread_mutex_t
typedef volatile struct __pthread_mutex_t {
unsigned int lock; /* LOCK, SLOW, TYPE, REFCNT */
unsigned int valid; /* Validation info */
__pthreadLongString_t name; /* Name of mutex */
unsigned int arg; /* printf argument for name */
unsigned int depth; /* Recursive lock depth */
unsigned long sequence; /* Mutex sequence number */
unsigned long owner; /* Current owner (if known) */
__pthreadLongAddr_p block; /* Pointer to blocking struct */
} pthread_mutex_t;
Page 64
Threads / Synchronisation – Claude Vilvens – HEPL (Informatique)
A un tel mutex peut correspondre des attributs, qui sont fixés au moyen d'un ensemble
d'attributs du type pthread_mutexattr_t; on peut utiliser pour ce faire la fonction
pthread_mutexattr_settype_np(). Le mutex est créé, avec les caractéristiques désirées, au
moyen de la fonction :
pour initialiser le mutex (--> "semctl" des sémaphores)
int pthread_mutex_init (<le mutex proprement dit - pthread_mutex_t *>,
<les attributs du mutex - const pthread_mutexattr_t *>);
pthread_mutex_init
paramètres
Pour un mutex usant de la configuration par défaut, il suffit d'utiliser NULL pour le
deuxième paramètre. La norme récente propose pour ce faire la constante
pthread_mutexattr_default.
pthread_mutex_init
erreurs
pthread_mutex_lock
erreurs
Page 65
Threads / Synchronisation – Claude Vilvens – HEPL (Informatique)
pthread_mutex_unlock
erreurs
int pthread_mutex_trylock (<le mutex que l'on tente de prendre - pthread_mutex_t *>);
pthread_mutex_trylock
erreurs
pthread_mutex_destroy
erreurs
Page 66
Threads / Synchronisation – Claude Vilvens – HEPL (Informatique)
Remarque
Un mutex statique peut s'initialiser en utilisant une macro
PTHREAD_MUTEX_INITIALIZER. Cela peut s'écrire :
Page 67
Threads / Synchronisation – Claude Vilvens – HEPL (Informatique)
mutex
Thread article
principal
stock
R1
?
S
voleur gérant client 1 client 2 client 5
SIGU
etc
S5s
SIGUSR2
SIGALRM
e? e ?
tur ur
up pt r e?
r ru tu
p
ru
cpt
cpt
cpt
?s
?s ?s
Page 68
Threads / Synchronisation – Claude Vilvens – HEPL (Informatique)
SUPERMARCHE07.C
/* SUPERMARCHE07.C
Claude Vilvens */
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sched.h>
#define affThread(num, msg) printf("th_%s> %s\n", num, msg)
#define N_MAX_CLIENTS 6
#define random(n) (rand()%(n))
pthread_t threadHandle;
pthread_t threadHandleVoleur;
pthread_t threadHandleGerant;
struct sigaction sigAct;
pthread_mutex_t mutexArticle;
#define NB_ART_DIFF 6
#define NB_ART_MAX 10
struct article magasins[] =
{
{1, "chocolat", 39, 2300}, la variable globale à protéger
{2, "whisky", 599, 2}, par le mutex
{3, "cereales", 67, 2600},
{4, "chips", 20, 5000},
{5, "preservatif", 2, 4000},
{6, "herbes aromatiques", 67, 3450}
};
Page 69
Threads / Synchronisation – Claude Vilvens – HEPL (Informatique)
/* ---------------------------- */
int nbClientEntres = 0;
char ouvert=0;
int *retThread;
int main()
{
int ret;
char * buf = (char *)malloc(80), rep, nouveauClient;
pthread_attr_t attrThrPrincipal;
struct sched_param schedP;
/* Initialisation */
affThread(getThreadIdentity(), "Thread principal demarre");
do
{
printf("Ouvrir le magasin ?");gets(buf);rep=buf[0];
}
while (rep!='O');
ouvert=1;
pthread_mutex_init(&mutexArticle, NULL);
sigAct.sa_handler = handlerVoleur;
if ( (ret=sigaction(SIGUSR1, &sigAct, 0)) == -1)
perror("\nErreur de sigaction sur SIGUSR1");
sigAct.sa_handler = handlerRuptureStock;
if ( (ret=sigaction(SIGUSR2, &sigAct, 0)) == -1)
perror("\nErreur de sigaction sur SIGUSR2");
Page 70
Threads / Synchronisation – Claude Vilvens – HEPL (Informatique)
pthread_cleanup_push(fctThreadClientFin,0);
sigemptyset(&mask);
sigaddset(&mask, SIGALRM);
sigprocmask(SIG_SETMASK, &mask, NULL);
…
pthread_testcancel();
if (ouvert)
{
do
{
nbArt = random(NB_ART_DIFF)+1;
}
while (nbArt<=0);
sprintf(buf,"... je compte acheter %d articles differents", nbArt);
affThread(numThr, buf);
fini=0;
do
{
int na = random(NB_ART_DIFF); no d'article
if (!exists(na, numAch, NB_ART_DIFF))
{
int nba = random(NB_ART_MAX)+1; nombre d'articles
int nbaReel;
pthread_mutex_lock(&mutexArticle);
/* pthread_lock_global_np(); */
if (magasins[na].quStock >= nba) stock suffisant
{
numAch[i]=na; i++;
nbaReel = nba;
section magasins[na].quStock-=nba;
critique }
else
stock insuffisant
{
nbaReel = nba - magasins[na].quStock;
if (nbaReel<=0) nbaReel=0;
else
{
numAch[i]=na; i++;
}
magasins[na].quStock = 0;
kill(getpid(), SIGUSR2); /* rupture de stock */
}
pthread_mutex_unlock(&mutexArticle);
Page 71
Threads / Synchronisation – Claude Vilvens – HEPL (Informatique)
/* pthread_unlock_global_np(); */
if (nbaReel >0)
{
sprintf(buf, "J'achete %d %s", nbaReel, magasins[na].libelle);
affThread(numThr, buf);
totalAPayer += nbaReel*magasins[na].prixUnitaire;
}
}
nEssai++;
pthread_testcancel();
fini = i==nbArt || ouvert==0;
}
while (!fini);
if (ouvert) sprintf(buf, "J'ai fini mon tour");
else sprintf(buf, "Tant pis ! Je n'ai plus le temps");
affThread(numThr, buf);
temps.tv_sec = rand()/5000; temps.tv_nsec = 0;
sprintf(buf, "Attente a la caisse de %d secondes ...",
temps.tv_sec);affThread(numThr, buf);
nanosleep(&temps, NULL);
}
else affThread(numThr, "Le magasin est ferme – trop tard :-(");
pthread_cleanup_pop(1);
pthread_exit(0);
return 0;
}
Page 72
Threads / Synchronisation – Claude Vilvens – HEPL (Informatique)
sigAct.sa_handler = handlerSignalAlarme;
sigaction(SIGALRM, &sigAct, NULL);
alarm(25);
sprintf(buf, "!-! Le gerant a enclenche son chrono");
affThread(numThr, buf);
temps.tv_sec = 10;temps.tv_nsec = 0;
sprintf(buf, "!-! Nous vous laissons %d secondes ...", temps.tv_sec);
affThread(numThr, buf);
nanosleep(&temps, NULL);
char * getThreadIdentity() { … }
Sur boole :
boole.inpres.epl.prov-liege.be> s
th_18953.1> Thread principal demarre
Ouvrir le magasin ?O
0. Nouveau client ! Bonjour !Thread secondaire lance !
1. Nouveau client ! Bonjour !Thread secondaire lance !
2. Nouveau client ! Bonjour !Thread secondaire lance !
!!!!!!!!!!! Au voleur !!!!!!!!!!!!
Page 73
Threads / Synchronisation – Claude Vilvens – HEPL (Informatique)
Page 74
Threads / Synchronisation – Claude Vilvens – HEPL (Informatique)
On remarquera que le traitement du signal SIGUSR2 est assuré par un thread quelconque
(celui qui a la main). Sur sunray, on obtient pour le même programme :
Page 75
Threads / Synchronisation – Claude Vilvens – HEPL (Informatique)
Page 76
Threads / Synchronisation – Claude Vilvens – HEPL (Informatique)
#define PTHREAD_MUTEX_NORMAL 0
Ils sont effectivement efficaces et sont caractérisés par le fait qu'un tel mutex peut être
verrouillé par un seul thread et, normalement, au maximum une fois. Donc, si un thread
verrouille un tel mutex qu'il a déjà verrouillé une fois, il y aura deadlock.
#define PTHREAD_MUTEX_RECURSIVE 1
#define PTHREAD_MUTEX_ERRORCHECK 2
Page 77
Threads / Synchronisation – Claude Vilvens – HEPL (Informatique)
pthread_mutexattr_settype
erreurs
7
valeur de retour erreur
0 succès
#define EINVAL 22 /* Invalid argument */ : le paramètre attribut n'est pas
valide
#define ESRCH 3 /* No such process */- le mutex visé n'existe pas
De plus, sur les machines Digital Unix mais pas sur les machines Sun Solaris, il existe
d'office un mutex global, par ailleurs récursif, mis en place par les librairies multi-threads. On
le manipule en utilisant les fonctions :
Ce mutex est intéressant pour utiliser des fonctions de librairies qui ne sont pas multithreads.
De telles fonctions sont en effet susceptibles de ne pas supporter la réentrance. L'utilisation du
mutex global évite le problème.
Il est évidemment indispensable que l'accès au compteur soit surveillé par un mutex qui
assurera l'atomicité des opérations ++ et --. L'événement auquel sera associé une variable de
condition sera le fait que le compteur d'activités retombe à zéro. On peut donc constater que
nous avons besoin d'un couple variable de condition-mutex.
Pour réaliser un petit programme implémentant cet exemple, il nous faut savoir
comment manier les variables de condition.
Page 78
Threads / Synchronisation – Claude Vilvens – HEPL (Informatique)
structure pthread_cond_t
typedef volatile struct __pthread_cond_t
{
unsigned int state; /* EVENT, SLOW, REFCNT */
unsigned int valid; /* Validation info */
__pthreadLongString_t name; /* Name of condition variable */
unsigned int arg; /* printf argument for name */
unsigned long sequence; /* Condition variable seq # */
__pthreadLongAddr_p block; /* Pointer to blocking struct */
} pthread_cond_t;
Comme pour les mutex, à une telle variable peut correspondre des attributs, qui sont fixés au
moyen d'un ensemble d'attributs du type pthread_condattr_t; on peut utiliser pour ce faire la
fonction pthread_condattr_init(). La variable est créée, avec les caractéristiques désirées, au
moyen de la fonction :
pthread_cond_init
paramètres
Pour un mutex usant de la configuration par défaut, il suffit d'utiliser NULL pour le
deuxième paramètre. La norme récente propose pour ce faire la constante
pthread_condattr_default.
pthread_cond_init
erreurs
Une fois que le mutex associé à la variable de condition a pu être verrouillé, la mise en attente
sur celle-ci se programme en utilisant la fonction :
Page 79
Threads / Synchronisation – Claude Vilvens – HEPL (Informatique)
♦ le mutex associé est déverrouillé (heureusement d'ailleurs, sans quoi il serait impossible
de modifier ailleurs la variable condition dont l'accès est précisément réglementé par ce
mutex);
♦ lorsque la condition est "signalée", autement dit lorsqu'une demande de réveil sur la
variable de condition est demandée par pthread_cond_signal(), le mutex est acquis par le
thread qui reprend son exécution; son premier travail sera de vérifier si l'événement
attendu s'est effectivement passé ou pas.
pthread_cond_wait
erreurs
Le paradigme classique du réveil d'un thread bloqué sur une variable de condition est alors :
Page 80
Threads / Synchronisation – Claude Vilvens – HEPL (Informatique)
pthread_cond_signal
erreurs
pthread_cond_destroy
erreurs
Excellent-Superbe-Sublime-Divin-
Une fois cette chaîne décomposée, un thread sera créé pour chaque nom, avec pour mission de
rechercher le nom en question et de fournir la réponse comportant le coefficient de réduction.
Histoire de tout mélanger, chaque thread est, tout à fait artificiellement, ralenti avec une mise
en sommeil aléatoire.
VARCONDI01.C
/* VARCONDI01.C
- Claude Vilvens -
*/
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
Page 81
Threads / Synchronisation – Claude Vilvens – HEPL (Informatique)
#include <unistd.h>
#include <signal.h>
#include <string.h> /* pour memcpy */
struct refClient
{
char nom[LONG_MAX_NOM];
float coeffRed;
};
Page 82
Threads / Synchronisation – Claude Vilvens – HEPL (Informatique)
/* 1. Initialisations */
puts("Thread principal serveur demarre");
pthread_mutex_init(&mutexNbreActivites, NULL);
pthread_cond_init(&condNbreActivites, NULL);
/* 2. Lecture du msgClient */
if (argc < 2)
{
puts("Usage : scan message_a_analyser"); exit(0);
}
strcpy(msgClient, argv[1]);
printf("msgClient = %s\n", msgClient);
cptNomsClients = 0;
token = strtok(msgClient, "-");
while (token != NULL)
{
nomsClientsRech[cptNomsClients] = malloc(strlen(token)+1);
strcpy(nomsClientsRech[cptNomsClients] , token);
cptNomsClients++;
if (cptNomsClients>=NBRE_MAX_NOMS_CLIENTS) break;
token = strtok((char *)NULL, "-");
}
puts("Fin scan msg client");
/* ------------------------------------------------------------ */
Page 83
Threads / Synchronisation – Claude Vilvens – HEPL (Informatique)
/* 1. Temporisation */
temps.tv_sec = random(15)+1;
temps.tv_nsec = 0;
sprintf(buf, "!-! Temps de reflexion de %d secondes ...", temps.tv_sec);
affThread(numThr, buf);
nanosleep(&temps, NULL);
pthread_exit(&vr);
return &vr;
}
char * getThreadIdentity() { … }
Page 84
Threads / Synchronisation – Claude Vilvens – HEPL (Informatique)
Excellent
Thread secondaire lance !
Marquage pour effacement du thread secondaire
Superbe
Thread secondaire lance !
Marquage pour effacement du thread secondaire
Sublime
Thread secondaire lance !
Marquage pour effacement du thread secondaire
Divin
Thread secondaire lance !
Marquage pour effacement du thread secondaire
th_7588.2> !-! Temps de reflexion de 9 secondes ...
th_7588.3> !-! Temps de reflexion de 14 secondes ...
th_7588.4> !-! Temps de reflexion de 4 secondes ...
th_7588.5> !-! Temps de reflexion de 11 secondes ...
th_7588.4> Sublime inconnu
th_7588.4> --fin du thread--
th_7588.2> Excellent trouve en 0 -> C.R.= 0.950
th_7588.2> --fin du thread--
th_7588.5> Divin trouve en 4 -> C.R.= 0.210
th_7588.5> --fin du thread--
th_7588.3> Superbe trouve en 3 -> C.R.= 0.870
th_7588.3> --fin du thread--
Fin du thread principal synchronisation
boole.INPRES.EPL.PROV-LIEGE.BE>
Page 85
Threads / Synchronisation – Claude Vilvens – HEPL (Informatique)
Schématiquement :
Page 86
Threads / Synchronisation – Claude Vilvens – HEPL (Informatique)
mutex
article
Thread stock
principal
SR1
etc
U
SIG
gestion-
voleur gérant client 1 client 2 client 5
Stock
S 10 s
SIGALRM
e?
wait [oui] ur
pt
[o
rupture [oui] ru
u
ruptureStock e?
i]
? ur
u re t
rupture pt r up
ru cpt
réappro-
visionner
cpt
cpt
?s
cond-
mutex
signal
?s ?s
Page 87
Threads / Synchronisation – Claude Vilvens – HEPL (Informatique)
ou en version plus
"compacte" :
Thread
principal
R1S
?
gestion-
SIGU
voleur gérant client 1 *
Stock
mutex
cond-wait
S5s
e ?
article
SIGALRM
t ur
[oui] p stock
wait ru
rupture
ruptureStock
?
rupture
cond-
signal
?s
réappro-
visionner
Page 88
Threads / Synchronisation – Claude Vilvens – HEPL (Informatique)
SUPERMARCHE08.C
/* SUPERMARCHE08.C
Claude Vilvens */
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <time.h>
#include <sched.h>
#define affThread(num, msg) printf("th_%s> %s\n", num, msg)
#define N_MAX_CLIENTS 6
#define random(n) (rand()%(n))
void * fctThreadClient(int * param);
void fctThreadClientFin (void *p);
void handlerSignalAlarme(int sig);
void handlerVoleur (int sig);
void handlerRuptureStock (int sig);
void * fctThreadGerant (int * param);
void * fctThreadGestionStock (int * param);
char * getThreadIdentity();
char exists(int n, int tab[], int dim);
void sleepThread(int ns);
pthread_t threadHandle;
pthread_t threadHandleVoleur;
pthread_t threadHandleGerant;
pthread_t threadHandleGestionStock;
struct sigaction sigAct;
pthread_mutex_t mutexArticle;
pthread_mutex_t mutexRupture;
pthread_cond_t condRupture;
Page 89
Threads / Synchronisation – Claude Vilvens – HEPL (Informatique)
#define NB_ART_DIFF 6
#define NB_ART_MAX 10
int nbClientEntres = 0;
char ouvert=0;
int *retThread;
int main()
{
int ret;
char * buf = (char *)malloc(80), rep, nouveauClient;
pthread_attr_t attrThrPrincipal;
struct sched_param schedP;
/* Initialisation */
affThread(getThreadIdentity(), "Thread principal demarre");
do
{
printf("Ouvrir le magasin ?");gets(buf);rep=buf[0];
}
while (rep!='O');
ouvert=1;
pthread_mutex_init(&mutexArticle, NULL);
pthread_mutex_init(&mutexRupture, NULL);
pthread_cond_init(&condRupture, NULL);
…
Page 90
Threads / Synchronisation – Claude Vilvens – HEPL (Informatique)
Page 91
Threads / Synchronisation – Claude Vilvens – HEPL (Informatique)
do
{
int na = random(NB_ART_DIFF);
if (!exists(na, numAch, NB_ART_DIFF))
{
int nba = random(NB_ART_MAX)+1;
int nbaReel;
pthread_mutex_lock(&mutexArticle);
if (magasins[na].quStock >= nba)
{
numAch[i]=na; i++; nbaReel = nba;
magasins[na].quStock-=nba;
}
else rupture de stock !
{
if (ruptureStock) continue;
section
nbaReel = nba - magasins[na].quStock;
critique if (nbaReel<=0) nbaReel=0;
else
{
numAch[i]=na; i++;
}
magasins[na].quStock = 0;
pthread_mutex_lock(&mutexRupture);
réveil du thread
ruptureStock++;
pthread_mutex_unlock(&mutexRupture); Gestion Stock
pthread_cond_signal(&condRupture);
}
pthread_mutex_unlock(&mutexArticle);
sprintf(buf, "J'achete %d %s", nbaReel, magasins[na].libelle);
affThread(numThr, buf);
totalAPayer += nbaReel*(magasins[na].prixUnitaire);
}
nEssai++;
fini = i==nbArt || ouvert==0;
}
while (!fini && ouvert);
if (ouvert) sprintf(buf, "J'ai fini mon tour");
else sprintf(buf, "Tant pis ! Je n'ai plus le temps");
affThread(numThr, buf);
temps.tv_sec = rand()/5000;
temps.tv_nsec = 0;
sprintf(buf, "Attente a la caisse de %d secondes ...", temps.tv_sec);
affThread(numThr, buf);
sprintf(buf, "Je paie a la caisse %d BEF", totalAPayer);
affThread(numThr, buf);
nanosleep(&temps, NULL);
}
else affThread(numThr,"Le magasin est ferme – trop tard :-(");
Page 92
Threads / Synchronisation – Claude Vilvens – HEPL (Informatique)
pthread_exit(0);
pthread_cleanup_pop(0);
return 0;
}
while(ouvert)
{
tant qu'il n'y a pas de rupture de stock,
pthread_mutex_lock(&mutexRupture);
while (!ruptureStock) j'attend...
pthread_cond_wait(&condRupture, &mutexRupture);
Page 93
Threads / Synchronisation – Claude Vilvens – HEPL (Informatique)
Observons ce qui se passe lorsque l'on exécute ce programme sur boole. Les décalages ne sont
pas réels : ils ont seulement pour but de mettre en correspondance les opérations effectuées
par le même client. On pourra ainsi constater qu'ils se terminent tous normalement. Le thread
de gestion de stock devra intervenir deux fois.
boole.inpres.epl.prov-liege.be> c
th_23151.1> Thread principal demarre
Ouvrir le magasin ?O
Thread gerant lance !
Thread de gestion du stock lance !
0. Nouveau client ! Bonjour !Thread secondaire lance !
1. Nouveau client ! Bonjour !Thread secondaire lance !
2. Nouveau client ! Bonjour !Thread secondaire lance !
!!!!!!!!!!! Au voleur !!!!!!!!!!!!
th_23151.1> !-! Le thread : 6 : Dehors !!!
3. Nouveau client ! Bonjour !Thread secondaire lance !
4. Nouveau client ! Bonjour !Thread secondaire lance !
5. Nouveau client ! Bonjour !Thread secondaire lance !
Page 94
Threads / Synchronisation – Claude Vilvens – HEPL (Informatique)
Page 95
Threads / Synchronisation – Claude Vilvens – HEPL (Informatique)
L'exécution du même programme sur copernic ou sunray n'apporte rien de plus. Le lecteur
méfiant pourrait cependant objecter que tout se passe bien parce qu'aucun client n'est
interrompu dans sa quête par une fermeture prématurée du magasin. Réduisons donc le temps
d'ouverture du magasin à 10 secondes. Sur copernic, cela donne :
sunray2v440.inpres.epl.prov-liege.be> thr8
th_10595.1> Thread principal demarre
Ouvrir le magasin ? O
Thread gerant lance !
th_10595.4> !-! Le gerant est dans le magasin
Thread de gestion du stock lance !
th_10595.4> !-! Le gerant a enclenche son chrono
0. Nouveau client ! Bonjour !Thread secondaire lance !
1. Nouveau client ! Bonjour !Thread secondaire lance !
2. Nouveau client ! Bonjour !Thread secondaire lance !
!!!!!!!!!!! Au voleur !!!!!!!!!!!!
th_10595.1> !-! Le thread : 8 : Dehors !!!
3. Nouveau client ! Bonjour !Thread secondaire lance !
4. Nouveau client ! Bonjour !Thread secondaire lance !
5. Nouveau client ! Bonjour !Thread secondaire lance !
== C'était le dernier client ==
th_10595.6> ... je compte acheter 5 articles differents
th_10595.6> J'achete 5 cereales
th_10595.6> J'achete 5 herbes aromatiques
th_10595.6> J'achete 2 preservatif
th_10595.6> J'achete 2 whisky
th_10595.6> J'achete 4 chips
th_10595.6> J'ai fini mon tour
th_10595.6> Attente a la caisse de 1 secondes ...
th_10595.6> Je paie a la caisse 1952 BEF
th_10595.6> Fin du thread client - il va sortir
th_10595.6> ... je passe à la caisse ...
th_10595.7> ... je compte acheter 3 articles differents
th_10595.7> J'achete 4 chips
th_10595.7> J'achete 5 cereales
th_10595.7> J'achete 4 preservatif
Page 96
Threads / Synchronisation – Claude Vilvens – HEPL (Informatique)
Page 97
Threads / Variables spécifiques – Claude Vilvens – HEP R. Sualem (Dépt. Informatique)
Page 98
Threads / Variables spécifiques – Claude Vilvens – HEP R. Sualem (Dépt. Informatique)
SPECIFICTHREAD01.C
/* specificThread01.c
Claude Vilvens */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#define MAX 18
void main()
{
int hFile, r;
int lireUneLigne(int h)
{
static int cptLectures = 0;
int ret;
char buf[MAX+1];
Page 99
Threads / Variables spécifiques – Claude Vilvens – HEP R. Sualem (Dépt. Informatique)
sunray2v440.inpres.epl.prov-liege.be> c
On a lu : 123 678 03/10/98
** Compteur = 1 **
On a lu : 333 675 25/20/99
** Compteur = 2 **
On a lu : 221 734 02/11/99
** Compteur = 3 **
sunray2v440.inpres.epl.prov-liege.be>
SPECIFICTHREAD02.C
/* specificThread02.c
Claude Vilvens
*/
#include <stdio.h>
#include <errno.h>
#include <pthread.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#define MAX 18
void main()
{
int hFile1, hFile2;
int rt, *retThread1, *retThread2;
Page 100
Threads / Variables spécifiques – Claude Vilvens – HEP R. Sualem (Dépt. Informatique)
void *lire(void * h)
{
static int vr;
int ha = (int)h;
Page 101
Threads / Variables spécifiques – Claude Vilvens – HEP R. Sualem (Dépt. Informatique)
Pour faire court, on n'a pas spécialement soigné les castings dans les appels des primitives de
threads. Des mises en sommeil aléatoires, comme d'habitude, permettent de donner un aspect
chaotique à notre essai. Mais le problème est bien sûr ailleurs : la variable statique
cptLectures est commune aux deux threads, si bien que le nombre de lignes lues pour chacun
des deux fichiers s'est additionné ! Ainsi, si le deuxième fichier stockInput600 contient :
sunray2v440.inpres.epl.prov-liege.be> c
Debut du thread principal 3223038392
Debut du thread 1073985920
** Compteur initial dans 1073985920 = 0 **
Attente de 3 secondes ...
Debut du thread 1074051456
** Compteur initial dans 1074051456 = 0 **
Attente de 1 secondes ...
On a lu : 983 899 23/04/00
** Compteur modifie dans 1074051456 = 1 **
-- vr dans thread 1074051456 = 19
** Compteur initial dans 1074051456 = 1 **
Attente de 2 secondes ...
On a lu : 123 678 03/10/98
** Compteur modifie dans 1073985920 = 2 **
-- vr dans thread 1073985920 = 19
** Compteur initial dans 1073985920 = 2 **
Attente de 3 secondes ...
On a lu : 954 843 03/08/99
** Compteur modifie dans 1074051456 = 3 **
-- vr dans thread 1074051456 = 19
** Compteur initial dans 1074051456 = 3 **
Attente de 6 secondes ...
On a lu : 333 675 25/20/99
** Compteur modifie dans 1073985920 = 4 **
-- vr dans thread 1073985920 = 19
** Compteur initial dans 1073985920 = 4 **
Page 102
Threads / Variables spécifiques – Claude Vilvens – HEP R. Sualem (Dépt. Informatique)
La variable statique compteur est bien la même pour tous les threads ! Ce n'est évidemment
pas ce que l'on souhaite et c'est ici que la possibilité de créer des variables statiques propres à
chaque thread va prouver son utilité.
Page 103
Threads / Variables spécifiques – Claude Vilvens – HEP R. Sualem (Dépt. Informatique)
appelés des clés. L'idée est alors qu'un thread quelconque pourra toujours accéder à une
donnée de "sa colonne".
4. Le principe de l'implémentation
La zone de mémoire statique réservée par le système à l'usage des threads peut donc
être vue comme une zone contiguë de mémoire gérée par un certain nombre de structures
appelées des clés [keys]; soit nc ce nombre de structures. Chaque structure comporte
clé[0] flag
pDestructeur
clé[1] flag
pDestructeur
…
clé[nc-1] flag
pDestructeur
Pour chaque thread, le système mémorise un certain nombre d'informations (un peu à
l'image de ce qui se passe pour un processus, mais, rappelons-le, en plus simple), dont
notamment un tableau de pointeurs associés aux clés. Le rôle de ces pointeurs est de désigner
la donnée effectivement mémorisée par l'intermédiaire de la clé. Donc, pour chaque
processus, on peut imaginer que les informations correspondantes ont la forme :
thread 0 thread 1
pointeur-clé[0] pointeur-clé[0]
pointeur-clé[1] pointeur-clé[1]
pointeur-clé[nc-1] pointeur-clé[nc-1]
Initialement, ces pointeurs sont nuls. Reste à voir comment on peut leur faire désigner une
zone statique à usage réservé au thread considéré et, aussi, comment utiliser de telles données.
1
on n'échappe jamais vraiment aux objets ;-) …
Page 104
Threads / Variables spécifiques – Claude Vilvens – HEP R. Sualem (Dépt. Informatique)
pthread_key_create
paramètres
Le premier paramètre est la clé obtenue; c'est une sorte d'index dans le tableau des
pointeurs. Elle est de type pthread_key_t, qui dissimule en fait (dans pthread.h) :
#ifndef _PTHREAD_ENV_UNIX
typedef unsigned int pthread_key_t;
#endif
Cette clé est connue de tous les threads, qui pourront donc l'utiliser chacun, mais pour
accéder chacun à une zone mémoire distincte. Autrement dit,
La zone effectivement associée à chaque thread est pour l'instant inexistante : le pointeur
correspondant est nul. Ce pointeur est cependant réservé et l'espace mémoire qu'il représente,
en fait, peut être utilisé, par exemple, comme un simple entier.
Il faut remarquer que cette création de clé ne doit être effectuée qu'une seule fois, sous
peine d'erreur.
Le deuxième paramètre permet de définir une fonction de terminaison dont le rôle est
de libérer les ressources associées à la clé – on peut donc franchement parler de destructeur au
sens de la P.O.O.
pthread_key_create
erreurs
Page 105
Threads / Variables spécifiques – Claude Vilvens – HEP R. Sualem (Dépt. Informatique)
pthread_key_delete
erreurs
Cette fonction renvoie le pointeur vers la zone, ou NULL si cette zone n'existe pas encore. A
l'inverse, un thread modifie sa donnée spécifique associée à une clé donnée par :
pthread_setspecific
erreurs
Page 106
Threads / Variables spécifiques – Claude Vilvens – HEP R. Sualem (Dépt. Informatique)
Thread
principal
thread 1 thread 2
lire() lire()
stock300 stock600 get/setspecific()
lireUneLigne() lireUneLigne()
heap
v.s.
clé 1
clé 2 cpt1 cpt2
eof() eof()
clé 3
data
SPECIFICTHREAD03.C
/* specificThread03.c
Claude Vilvens
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
Page 107
Threads / Variables spécifiques – Claude Vilvens – HEP R. Sualem (Dépt. Informatique)
#include <pthread.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#define MAX 18
void main()
{
int hFile1, hFile2;
int rt, *retThread1, *retThread2;
void *lire(void * h)
{
static int vr;
int ha = (int)h, cptFin;
Page 108
Threads / Variables spécifiques – Claude Vilvens – HEP R. Sualem (Dépt. Informatique)
Page 109
Threads / Variables spécifiques – Claude Vilvens – HEP R. Sualem (Dépt. Informatique)
Page 110
Threads / Variables spécifiques – Claude Vilvens – HEP R. Sualem (Dépt. Informatique)
Chaque fonction d'initialisation est associée à une structure pthread_once_t qui lui est
particulière. Le premier thread qui invoquera pthread_once() provoquera effectivement
l'exécution de la routine d'initialisation. Les autres pourront invoquer pthread_once() sans
provoquer d'effet (plus exactement, l'exécution de la routine d'initialisation est bloquée pour
ces threads jusqu'à la fin du thread qui a initialisé).
pthread_once
paramètres
Pour ce qui est du premier paramètre, il s'agit d'une structure qui permet
l'enregistrement de l'exécution de la routine d'initialisation. Dans pthread.h, elle est définie
par :
structure pthread_once_t
#ifndef _PTHREAD_ENV_UNIX
typedef volatile struct __pthread_once_t
{
long _Pfield(state);
long _Pfield(reserved)[10];
} pthread_once_t;
#endif
#define PTHREAD_ONCE_UNINIT 0
#define PTHREAD_ONCE_INITING 1
#define PTHREAD_ONCE_INITED 2
#define PTHREAD_ONCE_INIT {PTHREAD_ONCE_UNINIT}
pthread_once
erreurs
1
en C++, on parle encore de "singleton"
Page 111
Threads / Variables spécifiques – Claude Vilvens – HEP R. Sualem (Dépt. Informatique)
Le programme suivant a placé la création de la clé dans une routine d'initialisation exécutée
une seule fois :
SPECIFICTHREAD04.C
/* specificThread04.c
Claude Vilvens
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>
#define MAX 18
void main()
{
int hFile1, hFile2;
int rt, *retThread1, *retThread2;
…
}
void *lire(void * h)
{
static int vr;
int ha = (int)h, cptFin;
Page 112
Threads / Variables spécifiques – Claude Vilvens – HEP R. Sualem (Dépt. Informatique)
void initCle()
{
puts("=== initialisation d'une cle ===");
pthread_key_create(&cle, NULL);
}
Ce qui donne :
sunray2v440.inpres.epl.prov-liege.be> c
Debut du thread principal 3223038392
Debut du thread 1073985920
=== initialisation d'une cle === exécutée une seule fois
** Compteur initial dans 1073985920 = 0 **
Attente de 3 secondes ...
Debut du thread 1074051456
** Compteur initial dans 1074051456 = 0 **
Attente de 1 secondes ...
On a lu : 983 899 23/04/00
** Compteur modifie dans 1074051456 = 1 **
-- vr dans thread 1074051456 = 19
** Compteur initial dans 1074051456 = 1 **
Page 113
Threads / Variables spécifiques – Claude Vilvens – HEP R. Sualem (Dépt. Informatique)
Page 114
Threads / Variables spécifiques – Claude Vilvens – HEP R. Sualem (Dépt. Informatique)
thread 1 thread 2
pointeur-clé[nc-1] pointeur-clé[nc-1]
SPECIFICTHREAD05.C
/* specificThread05.c
Claude Vilvens
*/
#include <stdio.h>
#include <errno.h>
#include <pthread.h>
…
#define MAX 18
#define TAILLE_MSG 100
Page 115
Threads / Variables spécifiques – Claude Vilvens – HEP R. Sualem (Dépt. Informatique)
void main()
{
int hFile1, hFile2;
int rt, *retThread1, *retThread2;
…
}
void *lire(void * h)
{
static int vr;
int ha = (int)h, cptFin, index=0, j;
char * pSpec;
pthread_exit(&vr);
return 0;
}
return ret;
}
Page 116
Threads / Variables spécifiques – Claude Vilvens – HEP R. Sualem (Dépt. Informatique)
void initCle()
{
puts("=== initialisation d'une cle ===");
pthread_key_create(&cle, destructeur);
}
Thread
principal
thread 1 thread 2
lire() lire()
stock300 stock600
lireUneLigne()
lireUneLigne() heap
v.s.
clé 1
clé 2
eof() eof()
clé 3
data
Page 117
Threads / Variables spécifiques – Claude Vilvens – HEP R. Sualem (Dépt. Informatique)
Ce qui donne :
sunray2v440.inpres.epl.prov-liege.be> c
Debut du thread principal 3223038392
Debut du thread 1073985920
=== initialisation d'une cle ===
Attente de 3 secondes ...
Debut du thread 1074051456
Attente de 1 secondes ...
On a lu : 983 899 23/04/00
-- vr dans thread 1074051456 = 19
Attente de 2 secondes ...
On a lu : 123 678 03/10/98
-- vr dans thread 1073985920 = 19
Attente de 3 secondes ...
On a lu : 954 843 03/08/99
-- vr dans thread 1074051456 = 19
Attente de 6 secondes ...
On a lu : 333 675 25/20/99
-- vr dans thread 1073985920 = 19
Attente de 1 secondes ...
On a lu : 221 734 02/11/99
-- vr dans thread 1073985920 = 19
Attente de 4 secondes ...
On a lu : 943 855 20/12/00
-- vr dans thread 1074051456 = 19
Attente de 1 secondes ...
On a lu : 911 833 31/12/99
-- vr dans thread 1074051456 = 19
Attente de 3 secondes ...
-- pSpec dans thread 1073985920 = 331
=== liberation d'une zone specifique ===
Valeur de retour du thread 1 = 0
On a lu : 932 344 23/83/23
-- vr dans thread 1074051456 = 19
Attente de 0 secondes ...
On a lu : 992 565 02/04/88
-- vr dans thread 1074051456 = 19
Attente de 0 secondes ...
-- pSpec dans thread 1074051456 = 343122
=== liberation d'une zone specifique ===
Valeur de retour du thread 2 = 0
sunray2v440.inpres.epl.prov-liege.be
Quand on récapitule tout ce que nous savons à présent sur les threads
Posix, il paraît évident qu'ils ne sont pas si élémentaires qu'il y paraît au
premier abord. Terminons donc cet exposé en prenant un peu de recul …
Page 118
Threads / Modèles – Claude Vilvens – HEPL (Informatique)
Page 119
Threads / Modèles – Claude Vilvens – HEPL (Informatique)
Comme dans un chapitre précédent, les noms de clients faisant l'objet de la recherche
seront passés sur la ligne de commande, séparés par un "-". Pour chaque nom, trois threads
seront lancés (un par fichier); ils recevront comme paramètre une structure contenant le nom
du client à chercher, le nom du fichier à utiliser pour cette recherche et la position du nom
dans la liste :
struct critereRech
{
int numeroOrdre; Structure passée en paramètre à chaque thread
char nomFich[20];
char nomRech[20];
};
Il est implicitement supposé que le renseignement cherché est celui qui suit le nom (on
pourrait compliquer pour être plus réaliste, mais ceci est sans intérêt ici). Les résultats de la
recherche seront stockés dans un tableau de structure du type :
struct resultatRech
{
char nomClient[20];
int codeErreur;
char localite[30]; Stockage des résultats dans cette structure
int revenuMensuel;
int nbrePrescriptions;
};
le champ codeErreur permettant de détecter le cas d'un client non trouvé. Le thread principal
attendra que les trois threads aient terminé leur travail pour un nom donné avant de passer au
nom suivant. Schématiquement :
Page 120
Threads / Modèles – Claude Vilvens – HEPL (Informatique)
Thread
principal
requête
Page 121
Threads / Modèles – Claude Vilvens – HEPL (Informatique)
MODELEPARALLELE01.C
/* MODELEPARALLELE01.C
- Claude Vilvens -
*/
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h> /* pour exit */
#include <unistd.h>
#include <string.h> /* pour memcpy */
int ret;
Mémorisation des tid
pthread_t threadHandle[NB_MAX_THR_TRAITEMENT];
des threads
void * fctThread(void * param);
char * getThreadIdentity();
void sleepThread(int ns);
char * trim(char *s);
void insereRenseignement(char * r, int n, int c);
struct critereRech
{
int numeroOrdre; Paramètre des threads
char nomFich[20];
char nomRech[20];
} sCritereRech[NB_FICHIERS];
struct resultatRech
{
char nomClient[20];
int codeErreur;
char localite[30]; Stockage des résultats
int revenuMensuel;
int nbrePrescriptions;
} sResultatRecherche[NBRE_MAX_NOMS_CLIENTS];
#define R_CLI_OK 99
#define R_MAUVAIS_NUM_ORDRE 100
#define R_CLIENT_NON_TROUVE 101
Page 122
Threads / Modèles – Claude Vilvens – HEPL (Informatique)
int cpt;
char buf[100];
/* 1. Initialisations */
puts("* Thread principal serveur demarre *");
strcpy(sCritereRech[0].nomFich,"clients.data");
strcpy(sCritereRech[1].nomFich,"clients.fisc");
strcpy(sCritereRech[2].nomFich,"clients.sante");
/* 2. Lecture du msgClient */
if (argc < 2)
{
puts("Usage : scan message_a_analyser"); exit(0);
}
strcpy(msgClient, argv[1]);
printf("msgClient = %s\n", msgClient);
cpt = 0;
#if 0
token = strtok(msgClient, "-"); /* strtok n'est pas supporté en multithread */
#endif
Page 123
Threads / Modèles – Claude Vilvens – HEPL (Informatique)
cpt++;
/* ------------------------------------------------------------ */
void * fctThread (void *param)
{
char * nomCli = (char*)malloc(20),
* nomFich = (char *)malloc(20),
* ligneLue = (char *)malloc(100),
* buf = (char*)malloc(100),
* cherche, * encore,
* renseignement = (char *)malloc(30);
strcpy(nomCli, cr->nomRech);
strcpy(nomFich, cr->nomFich);
num = cr->numeroOrdre;
Page 124
Threads / Modèles – Claude Vilvens – HEPL (Informatique)
/* 1. Temporisation */
temps = random(10)+1;
sprintf(buf, "!-! Temps de reflexion de %d secondes ...", temps);
affThread(numThr, buf);
sleepThread(temps);
pthread_exit(&vr);
return (void *)vr;
}
char * getThreadIdentity(){ …}
Page 125
Threads / Modèles – Claude Vilvens – HEPL (Informatique)
Il importe évidemment que chaque thread dispose de sa propre structure FILE, sans quoi tous
les threads, sauf le premier, trouveront le pointeur de lecture déjà à la fin du fichier !
boole.inpres.epl.prov-liege.be>m UnMaitre-Excellent-Superbe-Sublime-Divin-
* Thread principal serveur demarre *
msgClient = UnMaitre-Excellent-Superbe-Sublime-Divin-
-------- Analyse de msgClient
Page 126
Threads / Modèles – Claude Vilvens – HEPL (Informatique)
….
Page 127
Threads / Modèles – Claude Vilvens – HEPL (Informatique)
Thread
principal
requête
etc
Code différent
ou
data
Page 128
Threads / Modèles – Claude Vilvens – HEPL (Informatique)
Un thread particulier (souvent le thread principal ou un thread créé à cet effet) reçoit
des requêtes de divers clients par un moyen quelconque (par exemple, par terminal, par
réseau, par ligne de commande, par fichier batch, …). Chaque requête est prise en charge par
un thread qui
Que l'on utilise la première ou la deuxième alternative, le thread principal fait bien
office de producteur de tâches tandis que les threads sont des consommateurs de ces
tâches. Chacun des threads consommateurs ignore ce que font les autres; ils dépendant tous
du serveur maître qui les a créés : ils en sont donc les esclaves. Cependant, les différents
threads exécutent tous obligatoirement le même code, d'où l'importance de disposer de code
réentrant.
Page 129
Threads / Modèles – Claude Vilvens – HEPL (Informatique)
Thread
principal
requête
Shut-
A chaque requête reçue, un thread est créé
down ?
etc
se terminent
après leur
[fini]
"job"
]
ni
[fi
]
[fini
data
wait
var.
var. événement cond.
mutex Pas nécessaire au modèle à la demande
Page 130
Threads / Modèles – Claude Vilvens – HEPL (Informatique)
Page 131
Threads / Modèles – Claude Vilvens – HEPL (Informatique)
Thread
principal
etc
thread 1 thread 2
fct() fct()
wait wait
cptNoms- cptNoms-
cptNomsClients Clients cptNomsClients Clients
cptNomsClients cptNomsClients
signal signal
[client]
requête
signal [client]
[fini]
cpt
fichierclients
wait [fini]
nbreNomsClients- NbreActivites
NonTraites
NbreActivites
Page 132
Threads / Modèles – Claude Vilvens – HEPL (Informatique)
MODELEPRODUCCONSOM01.C
/* MODELEPRODUCCONSOM01.C
- Claude Vilvens -
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <string.h> /* pour memcpy */
struct refClient
{
char nom[LONG_MAX_NOM];
float coeffRed;
};
pthread_t threadHandle[NB_MAX_THR_CLIENTS];
pthread_mutex_t mutexNbreActivites;
pthread_mutex_t mutexCptNomsClients;
pthread_cond_t condNbreActivites;
pthread_cond_t condCptNomsClients;
Page 133
Threads / Modèles – Claude Vilvens – HEPL (Informatique)
int cptNomsClients=0;
/* 1. Initialisations */
puts("* Thread principal serveur demarre *");
pthread_mutex_init(&mutexNbreActivites, NULL);
pthread_mutex_init(&mutexCptNomsClients, NULL);
pthread_cond_init(&condNbreActivites, NULL);
pthread_cond_init(&condCptNomsClients, NULL);
/* 2. Lecture du msgClient */
if (argc < 2)
{
puts("Usage : scan message_a_analyser");
exit(0);
}
strcpy(msgClient, argv[1]);
printf("msgClient = %s\n", msgClient);
cpt = 0;
token = strtok(msgClient, "-");
while (token != NULL) Réception d'une nouvelle requête
{
nomsClientsRech[cpt] = malloc(strlen(token)+1);
Page 134
Threads / Modèles – Claude Vilvens – HEPL (Informatique)
strcpy(nomsClientsRech[cpt] , token);
cpt++;
pthread_mutex_lock(&mutexCptNomsClients);
cptNomsClients++; on réveille un des threads
pthread_mutex_unlock(&mutexCptNomsClients); du pool
pthread_cond_signal(&condCptNomsClients);
if (cptNomsClients>=NBRE_MAX_NOMS_CLIENTS) break;
token = strtok((char *)NULL, "-");
}
puts("Fin scan msg client");
pthread_mutex_lock(&mutexCptNomsClients);
nbreNomsClientsNonTraites = cptNomsClients;
pthread_mutex_unlock(&mutexCptNomsClients);
sleepThread(cpt*3);
/* for (i=0; i<NB_MAX_THR_CLIENTS; i++)
{
ret = pthread_detach(threadHandle[i]);
printf("Thread secondaire %d arrete !\n", i);
}*/
puts("** Fin du thread principal **");
return 0;
}
/* ------------------------------------------------------------ */
void * fctThread (void *param)
{
char * nomCli, *buf = (char*)malloc(100);
int vr = (int)(param), trouve, i, iCliTraite;
int temps;
char * numThr = getThreadIdentity();
while (1)
{
/* 1. Attente d'un nom a traiter */ Chaque thread attend qu'une requete soit dispo
pthread_mutex_lock(&mutexCptNomsClients);
while (!cptNomsClients)
pthread_cond_wait(&condCptNomsClients, &mutexCptNomsClients);
iCliTraite = indiceCourant;
nomCli = (char *)malloc(strlen(nomsClientsRech[iCliTraite])+1);
Page 135
Threads / Modèles – Claude Vilvens – HEPL (Informatique)
/* 2. Temporisation */
temps = random(10)+1; sleepThread(temps);
sprintf(buf, "!-! Temps de reflexion de %d secondes ...", temps);
affThread(numThr, buf);
char * getThreadIdentity() { … }
void sleepThread(int ns) { … }
Page 136
Threads / Modèles – Claude Vilvens – HEPL (Informatique)
msgClient = Excellent-Superbe-Sublime-Divin-Infect-Extraordinaire-Ecoeurant-Mazette-
Thread secondaire 0 lance !
Thread secondaire 1 lance !
Thread secondaire 2 lance !
Thread secondaire 3 lance !
Thread secondaire 4 lance !
-------- Analyse de msgClient
Fin scan msg client
Attente de la fin du traitement de tous les noms ...
nbreNomsClientsNonTraites = 7
th_75119.5> Je m'occupe de Divin (3) ...
th_75119.3> Je m'occupe de Superbe (1) ...
th_75119.4> Je m'occupe de Sublime (2) ...
th_75119.6> Je m'occupe de Infect (4) ...
th_75119.2> Je m'occupe de Excellent (0) ...
th_75119.2> !-! Temps de reflexion de 2 secondes ...
th_75119.2> Excellent trouve en 0 -> C.R.= 0.950
th_75119.4> !-! Temps de reflexion de 4 secondes ...
th_75119.4> Sublime inconnu
th_75119.6> !-! Temps de reflexion de 6 secondes ...
th_75119.6> Infect inconnu
th_75119.2> nbreNomsClientsNonTraites tombe à 6
th_75119.2> Je m'occupe de Extraordinaire (5) ...
th_75119.5> !-! Temps de reflexion de 9 secondes ...
th_75119.5> Divin trouve en 4 -> C.R.= 0.210
th_75119.4> nbreNomsClientsNonTraites tombe à 5
th_75119.4> Je m'occupe de Ecoeurant (6) ...
th_75119.3> !-! Temps de reflexion de 9 secondes ...
th_75119.3> Superbe trouve en 3 -> C.R.= 0.870
th_75119.4> !-! Temps de reflexion de 1 secondes ...
th_75119.4> Ecoeurant trouve en 6 -> C.R.= 0.670
th_75119.6> nbreNomsClientsNonTraites tombe à 4
th_75119.6> Je m'occupe de Mazette (7) ...
th_75119.5> nbreNomsClientsNonTraites tombe à 3
th_75119.3> nbreNomsClientsNonTraites tombe à 2
th_75119.2> !-! Temps de reflexion de 8 secondes ...
th_75119.2> Extraordinaire trouve en 8 -> C.R.= 0.890
th_75119.4> nbreNomsClientsNonTraites tombe à 1
th_75119.2> nbreNomsClientsNonTraites tombe à 0
th_75119.6> !-! Temps de reflexion de 10 secondes ...
th_75119.6> Mazette inconnu
th_75119.6> nbreNomsClientsNonTraites tombe à 0
Thread secondaire 0 arrete !
Thread secondaire 1 arrete !
Thread secondaire 2 arrete !
Thread secondaire 3 arrete !
Thread secondaire 4 arrete !
** Fin du thread principal **
boole.inpres.epl.prov-liege.be>
Page 137
Threads / Modèles – Claude Vilvens – HEPL (Informatique)
Thread
principal
etc
thread 1 thread 2
même
code
fct() fct()
tournent
en boucle ! Attente sur la condition
wait wait
var.cond. var.cond.
variableEvt variableEvt
mutex mutex
[evt]
requête
signal [evt]
réveil d'un thread via la VC
Shut-
down ?
data
Page 138
Threads / Modèles – Claude Vilvens – HEPL (Informatique)
4. Le modèle du pipeline
4.1 Le principe du modèle
C'est typiquement le modèle utilisé dans les grands programmes de calcul, où
l'ampleur de la tâche réclame un éclatement des opérations et un certain travail simultané pour
espérer obtenir les résultats dans un délai acceptable. Les principes généraux sont les suivants.
♦ un premier thread TH_P produit des données ou les reçoit par une voie de communication
quelconque; les données sont placées au fur et à mesure dans une structure de données SD1
(un vecteur, une file, éventuellement un fichier);
♦ un deuxième thread TH_2 surveille cette structure de données SD1; dès qu'une donnée y
est placée, il s'en empare pour la traiter puis, ce traitement effectué, place le résultat dans une
autre structure de données SD2;
♦ celle-ci est sous la surveillance d'un troisième thread TH_3; dès qu'une donnée y apparaît,
il la traite et place le résultat dans une nouvelle structure de données SD3;
♦ et ainsi de suite jusqu'à voir arriver des données dans une structure SDn :
♦ un dernier thread TH_R prend la donnée suivante dans SDn, effectue éventuellement un
dernier traitement puis fournit le résultat final.
En résumé,
Dans ce genre de modèle, tout l'art consiste évidemment à découper judicieusement l'énorme
tâche globale en sous-tâches assurant un maximum de fluidité dans la chaîne de traitement.
Page 139
Threads / Modèles – Claude Vilvens – HEPL (Informatique)
Page 140
Threads / Modèles – Claude Vilvens – HEPL (Informatique)
Thread
principal
T
thread 1 thread 3
SIGIN
thread 2
Commande TraiteCommande
SelectCommande
listeCommandes
listeFabrications
fct1() fct2() fct3()
signal [commande]
commandes.data fabrication.data
Page 141
Threads / Modèles – Claude Vilvens – HEPL (Informatique)
MODELEPIPELINE01.C
/* MODELEPIPELINE01.C
Claude Vilvens
*/
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <signal.h>
#include <time.h>
#define NB_MAX_COMMANDES 50
#define random(n) (rand()%(n))
pthread_t threadHandleCommande;
pthread_t threadHandleSelectCommande;
pthread_t threadHandleTraiteCommande;
struct sigaction sigAct;
pthread_mutex_t mutexCommande;
pthread_mutex_t mutexFabrication;
pthread_mutex_t mutexLog;
pthread_cond_t condCommande;
pthread_cond_t condFabrication;
/* ---------------------------- */
/* Pour les articles commandes */
struct commande
{
int num;
char client[30];
int quantite;
int delai;
char enFabrication;
};
Page 142
Threads / Modèles – Claude Vilvens – HEPL (Informatique)
int main()
{
char * buf = (char *)malloc(80), rep, nouveauClient;
int ret;
/* Initialisation */
puts("* Thread principal demarre *");
fLog=fopen("commandes.log","w");
retThread = (int *)malloc(sizeof(int));
pthread_mutex_init(&mutexCommande, NULL);
pthread_mutex_init(&mutexFabrication, NULL);
pthread_mutex_init(&mutexLog, NULL);
pthread_cond_init(&condCommande, NULL);
pthread_cond_init(&condFabrication, NULL);
Page 143
Threads / Modèles – Claude Vilvens – HEPL (Informatique)
sigAct.sa_handler = handlerInt;
if ( (ret=sigaction(SIGINT, &sigAct, 0)) == -1)
perror("\nErreur de sigaction sur SIGINT");
int ancEtat;
if (pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, &ancEtat))
puts("Erreur de setcancelstate");
if (pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, &ancEtat))
puts("Erreur de setcanceltype");
do
{
printf("Nouvel article a commander ...\n");
pthread_testcancel();
printf("Numero : ");gets(buf); ac.num=atoi(buf);
printf("Nom client : "); gets(ac.client);
printf("Quantite : "); scanf("%d",&ac.quantite);
printf("Delai : ");scanf("%d", &ac.delai);
ac.enFabrication = 0;
fflush(stdin);
sleepThread(random(5)+1);
pthread_mutex_lock(&mutexCommande);
insereCommande(ac); on réveille le thread
nbreCommandes++; suivant dans la chaine
pthread_mutex_unlock(&mutexCommande);
pthread_cond_signal(&condCommande);
/*ret1 = 10;
pthread_exit(&ret1);*/
return 0;
}
Page 144
Threads / Modèles – Claude Vilvens – HEPL (Informatique)
int ancEtat;
if (pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, &ancEtat))
puts("Erreur de setcancelstate");
if (pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, &ancEtat))
puts("Erreur de setcanceltype");
f=fopen("commandes.data","w");
do
{ attente d'une tâche
pthread_mutex_lock(&mutexCommande);
while (nbreCommandes<=0)
pthread_cond_wait(&condCommande, &mutexCommande);
acCours = listeCommandes[ptCom];
ptCom++;if (ptCom== NB_MAX_COMMANDES) ptCom=0;
nbreCommandes--;
pthread_mutex_unlock(&mutexCommande);
if (acCours.enFabrication==0)
{
acCours.enFabrication=1;
fwrite(&acCours,sizeof(struct commande), 1, f);
fflush(f); le thread fait son job
attente=random(5)+1;
sprintf(buf, "Select> temps attente : %d\n", attente); --> réajustement du
ecritLog(buf); temps de fabrication
sleepThread(attente);
tempsTrouve = getTempsFabrication(acCours.num);
if (acCours.delai < tempsTrouve) acCours.delai = tempsTrouve;
pthread_mutex_lock(&mutexFabrication);
insereFabrication(acCours); on réveille le thread
pthread_mutex_unlock(&mutexFabrication);
pthread_cond_signal(&condFabrication); suivant dans la chaine
Page 145
Threads / Modèles – Claude Vilvens – HEPL (Informatique)
ecritLog(buf);
}
fflush(fLog);
}
while (1);
pthread_exit(0);
return 0;
}
int ancEtat;
if (pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, &ancEtat))
puts("Erreur de setcancelstate");
if (pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, &ancEtat))
puts("Erreur de setcanceltype");
f=fopen("fabrications.data","w");
do
{ Attente d'une tâche
pthread_mutex_lock(&mutexFabrication);
while (ptFab==finFab)
pthread_cond_wait(&condFabrication, &mutexFabrication);
acCours = listeCommandes[ptFab];
ptFab++;if (ptFab== NB_MAX_COMMANDES) ptFab=0;
pthread_mutex_unlock(&mutexFabrication);
fwrite(&acCours,sizeof(struct commande), 1, f);
fflush(f);
attente = acCours.delai*acCours.quantite;
sprintf(buf, "Traite> temps attente : %d\n", attente); le thread fait son job
ecritLog(buf); --> calcul temps total
sleepThread(acCours.delai*acCours.quantite);
sprintf(buf, "Traite> article : %d\n", acCours.num);
ecritLog(buf);
}
while (1);
pthread_exit(0);
return 0;
}
Page 146
Threads / Modèles – Claude Vilvens – HEPL (Informatique)
pthread_mutex_lock(&mutexLog);
t = time(0);
pt = gmtime(&t);
sprintf(tbuf,"[%d:%d:%d]: %s",pt->tm_hour, pt->tm_min, pt->tm_sec, buf);
fputs(tbuf, fLog);
fflush(fLog);
pthread_mutex_unlock(&mutexLog);
}
copernic.inpres.epl.prov-liege.be> m
* Thread principal demarre *
Thread commandes lance !
Thread de selection des commandes lance !
Thread de traitement des commandes lance !
Nouvel article a commander ...
Page 147
Threads / Modèles – Claude Vilvens – HEPL (Informatique)
Numero : 345
Nom client : VIL
Quantite : 10
Delai : 3
Nouvel article a commander ...
Numero : 765
Nom client : BAST
Quantite : 20
Delai : 2
Nouvel article a commander ...
Numero : 987
Nom client : DEL
Quantite : 30
Delai : 1
Nouvel article a commander ...
Numero : 287
Nom client : CLER
Quantite : 25 un méchant CTRL-C
Delai : 2
Nouvel article a commander ...
Numero : --- Arret total ---
- Arret de Commande -
- Arret de SelectCommande -
- Arret de TraiteCommande -
Nom client : --- Arret total ---
- Arret de Commande -
- Arret de SelectCommande -
- Arret de TraiteCommande -
Quantite : --- Arret total ---
- Arret de Commande -
- Arret de SelectCommande -
- Arret de TraiteCommande -
Delai : --- Arret total ---
- Arret de Commande -
- Arret de SelectCommande -
- Arret de TraiteCommande -
** Fin du thread principal **
copernic.inpres.epl.prov-liege.be>
Page 148
Threads / Modèles – Claude Vilvens – HEPL (Informatique)
Page 149
Threads / Modèles – Claude Vilvens – HEPL (Informatique)
Thread codes
principal différents
T
SIGIN
thread 1 thread i * thread n
(i=2 .. n-1)
Page 150
Threads / Modèles – Claude Vilvens – HEPL (Informatique)
Page 151
Threads / Symboles – Claude Vilvens – HEPL (Informatique)
mise en sommeil
fonction éxécutée
captage d'un signal par un thread()
requête
data
accès à des données
variables statiques (globales)
mutex data
heap
wait wait sur une variable variables dynamiques (allouées)
var. de condition
var. événement cond.
mutex v.s.
clé 1
signal sur une clé 2
[événement]
variable de condition
var. clé 3
cond. variables spécifiques
Annexe 1 : Page 1
Threads / Librairies – Claude Vilvens – HEPL (Informatique)
(Zazie, A ma place)
1. L'objectif : réutiliser
Les librairies1 constituent l'un des éléments fondamentaux de l'arsenal des outils de
programmation. Elles contribuent à la réalisation d'un objectif omniprésent dans les
démarches de la programmation : la réutilisation, au sein d'une nouvelle application, de code
déjà écrit, déjà testé, déjà mis au point et déjà compilé dans un autre contexte. Tous les
systèmes d'exploitation dignes de ce nom proposent cette notion. UNIX les possède donc
aussi, offrant les librairies "système" (comme celle des threads …) et la possibilité de créer
ses propres librairies.
Dans sa forme la plus simple, on pourrait considérer qu'une librairie se présente sous
la forme du résultat d'une compilation, soit un fichier objet d'extension .o. Mais la notion est
plus large : une librairie est en fait un fichier qui contient un ou plusieurs fichiers objets, ces
derniers étant appelés des modules ou des membres de la librairie. Ces modules sont
mémorisés selon un certain format, avec une table associant les noms symboliques de la
librairie (par exemple, le nom d'une fonction) au module dans lequel le symbole est
effectivement défini. Ceci permet de retrouver rapidement la définition d'un symbole
quelconque lors du linkage (pardon, je voulais dire "l'édition de liens") de la librairie avec les
autres fichiers objets de l'application.
Une librairie est dite statique si son code objet est linké à celui de l'application qui
l'utilise (autrement dit, physiquement copié dans celui-ci) pour fournir un exécutable.
Cet exécutable est "auto-suffisant" : il peut fonctionner sur n'importe quelle machine
UNIX compatible. On peut cependant remarquer que si plusieurs applications utilisent la
même librairie statique, chaque exécutable contiendra le code de la librairie. De plus, si la
librairie est modifiée ultérieurement, il faudra relinker toutes les applications utilisatrices avec
la nouvelle version.
Une librairie est par contre dite dynamique si le code de ses fonctions est linké et
chargé seulement au moment de l'exécution de l'application qui l'utilise (au "runtime").
1
comme on sait, "librairie" est ici une traduction malheureuse du mot anglais "library" – il faudrait dire
"bibliothèque" ou alors utiliser "bookshop" ;-) …
Annexe 2 - Page 1
Threads / Librairies – Claude Vilvens – HEPL (Informatique)
♦ leur code n'est pas linké statiquement avec celui de chacune des applications qui les
utilisent; ceci implique que l'on occupera inévitablement moins d'espace disque puisque
le code des applications sera moins grand.
♦ leur code est donc en fait partagé par les applications utilisatrices – c'est pour cette raison
que le monde UNIX parle encore de "librairies partagées" [shared library]. Elles ne sont
donc chargées en mémoire qu'une seule fois (lors de la première utilisation); ceci
implique que l'on occupera inévitablement moins d'espace mémoire.
3. Les conventions
Pour faciliter la gestion des librairies, un certain nombre de conventions ont été
établies :
Par exemple, si nous nous intéressons à la librairie dynamique des threads sur la
machine Copernic :
copernic> ls -l /usr/shlib/libpthread.so
donc il s'agit d'un lien vers /shlib/libpthread.so qui lui est un vrai fichier :
Bien sûr, un nouveau numéro principal signifie que la nouvelle version est
incompatible avec l'ancienne, un nouveau numéro secondaire signifie que la nouvelle version
1
l'application avec librairie statique est un voyageur qui emmène sa tente et ses vivres, l'application avec
librairie dynamique va à l'hôtel …
Annexe 2 - Page 2
Threads / Librairies – Claude Vilvens – HEPL (Informatique)
est cette fois compatible tandis qu'un nouveau numéro de patch indique que quelques bugs ont
été corrigés.
En réalité, le link dynamique n'est réalisé ni sur le nom raccourci (.so), ni sur le nom
complet (par exemple, .so.2.1.8), mais sur un nom de forme intermédaire, le soname. Ce
soname est le nom de la librairie complété du numéro principal de version (pour notre
exemple, .so.2). Faut-il le préciser ? Des liens symboliques relient les trois formes de nom …
Remarque
Pour la version archive statique, on obtient pour ls -l :
copernic> nm /usr/shlib/libpthread.so
Annexe 2 - Page 3
Threads / Librairies – Claude Vilvens – HEPL (Informatique)
L'affichage résultant fournit non seulement les symboles, mais utilise aussi une initiale
pour chacun d'entre eux indiquant la manière dont le symbole est défini (ou non défini). Les
plus courantes sont :
copernic> ldd c
Main => c
libpthread.so => /usr/shlib/libpthread.so
libc.so => /usr/shlib/libc.so
libexc.so => /usr/shlib/libexc.so
copernic>
Annexe 2 - Page 4
Threads / Librairies – Claude Vilvens – HEPL (Informatique)
progArith.h
#ifndef PROGARITH_H
#define PROGARITH_H
#endif
et
progArith.c
#include "progArith.h"
En faire une librairie statique n'est pas bien compliqué. Commençons par construire le
fichier objet :
copernic> cc -c progArith.c
ce qui nous produit un fichier progArith.o. Ensuite, nous allons effectivement créer le fichier
librairie en utilisant la commande d'archivage :
options effet
-c crée le fichier archive précisé si il n'existe pas encore
-r place les fichiers précisés dans l'archive; les symboles existant sont remplacés
par leur nouvelle version, les nouveaux symboles sont placés à la fin
-s met à à la table des associations symbole-membre
Pour ce qui nous intéresse, si nous choisissons de baptiser notre librairie libpa (pour rappel,
débuter par "lib" est une convention), cela donnera :
Annexe 2 - Page 5
Threads / Librairies – Claude Vilvens – HEPL (Informatique)
On peut constater que le fichier libpa.a a bien été créé. Son contenu peut être vérifié avec la
commande nm :
libpa.a[progArith.o]:
Effectivement, seul le symbole "terme" est défini au sein de cette librairie, tandis que le
symbole "puts" est bien utilisé mais non défini.
testProgArith.c
/* test de progArith */
#include <stdio.h>
#include "progArith.h"
int main ()
{
float a,r;
int num;
scanf("%f",&a); /* c'était vraiment trop dur de placer quelques printf() ??? ;-) */
scanf("%f", &r);
scanf("%d",& num);
printf("Resultat = %f\n", terme(a,r,num));
return 0;
}
Formidable, n'est-il pas ? Il nous faut donc le compiler et réaliser l'édition de liens avec notre
bibliothèque. Celle-ci sera désignée selon le commutateur :
On remarquera l'utilisation de la convention selon laquelle une librairie porte toujours un nom
débutant par "lib". Dans notre cas, cela donnera donc –lpa – ceci rappelle bien –lpthread ou
-lm. Mais, précisément, ces librairies système se trouvent dans des répertoires prédéfinis
(usuellement /usr/lib)). Ce n'est pas le cas de notre librairie : il nous faut donc préciser dans
quel répertoire elle se trouve au moyen du commutateur
Annexe 2 - Page 6
Threads / Librairies – Claude Vilvens – HEPL (Informatique)
Pour nous, ce sera le répertoire courant. Il nous faut aussi préciser que notre librairie est
statique : en effet, de nombreux UNIX (dont Linux) supposent par défaut que la librairie
évoquée est dynamique, ce qui n'est pas le cas ici. Le commutateur nécessaire pou cc est
-non_shared (-static sous Linux).
libpa.a: progArith.o
ar rcs libpa.a progArith.o
copernic> progA
23
42
2
Hello
Resultat = 65.000000
copernic>
Annexe 2 - Page 7
Threads / Librairies – Claude Vilvens – HEPL (Informatique)
En fait, on peut vérifier que la variable DT_SONAME est positionnée. Par défaut, elle
désigne le nom du fichier de sortie.
Comme notre librairie ne se trouve pas dans /usr ou /usr/lib, il nous appartient de créer
les liens symboliques entre ce fichier et ses correspondants soname et link. Pour ce faire, on
utilise la commande des liens :
Par défaut, ln produit des liens 'hardware'; nous utiliserons ici des liens 'symboliques'
(commutateur –s). Pour ce qui nous concerne, nous allons lier :
♦ le fichier objet partagé et le soname, ce qui sera fort utile au loader dynamique : donc
tandis que
copernic> ls -l libparith.so.1.0.0
Attention ! Les liens symboliques ne se voient pas dans la ligne du répertoire (seulement les
liens hardware) …
♦ le soname et le fichier .so qui sera pris en compte par le linker ld (qui verra sur sa ligne de
commande –lparith, d'après les conventions de librairies) : donc
et au total :
Annexe 2 - Page 8
Threads / Librairies – Claude Vilvens – HEPL (Informatique)
Tout semble logique, la compilation s'effectue mais pourtant une tentative d'exécution de
testShared donnera :
copernic> env
TERM=vt220
SHELL=/bin/csh
USER=vilvens
…
LD_LIBRARY_PATH=/prof/vilvens/c
copernic>
copernic> testShared
67
23
45
Hello
Resultat = 1079.000000
copernic>
Annexe 2 - Page 9
Threads / Librairies – Claude Vilvens – HEPL (Informatique)
#include "../c/progArith.h"
Bof …
ld:
Can't locate file for: -lparith
copernic>
copernic> testShared
345
43523
323
Hello
Resultat = 14014751.000000
copernic>
Attention : so_locations doit rester dans son répertoire initial. Si il est dans déplacé
dans un autre répertoire, disons 'tcp' :
donne
ld:
Can't locate file for: - lparith
Même avec :
Annexe 2 - Page 10
Threads / Sources – Claude Vilvens – HEPL (Informatique)
Ouvrages consultés
Beaulen, M. Les threads – Programmation selon la norme Posix. Seraing, Belgique. A.S.B.L.
DEFI. 1998.
Janssens, A. UNIX sous tous les angles. Paris, France. Ed. Eyrolles. 1992.
Richter, J. Développer sous Windows 95 et Windows NT 4.0. Les Ulis, France. Microsoft
Press. 1997.
Stevens, W.R. UNIX networking programming – Networking APIs : Sockets and XTI
(Volume 1). U.S.A. Prentice Hall Pub. 1998.