Commandes Linux pour gérer les processus
Commandes Linux pour gérer les processus
Lakhdar Loukil
Mustapha Mokaddem
Université Oran 1
Département d’informatique
Année universitaire: 2020-2021
1
Contents
1 Commandes Linux pour afficher des informations sur les processus 3
1.1 La commande ps . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.1.1 Liste des processus du système . . . . . . . . . . . . . . . . . . . 3
1.1.2 Liste complète des processus . . . . . . . . . . . . . . . . . . . . 4
1.1.3 Liste des processus d’un utilisateur spécifique . . . . . . . . . . 5
1.1.4 Liste des processus par nom ou par PID . . . . . . . . . . . . . 5
1.1.5 Tri des processus par utilisation du CPU et de la mémoire . . . 6
1.2 La commande pstree . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
1.3 La commande top . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
1.4 Le pseudo-système de fichiers /proc . . . . . . . . . . . . . . . . . . . . 10
1.5 Exercices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
4 Le multithreading 24
4.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
4.2 Les threads POSIX (Pthreads) . . . . . . . . . . . . . . . . . . . . . . . 25
4.3 Exercices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
7 Références bibliographiques 47
2
1 Commandes Linux pour afficher des informations
sur les processus
Le système d’exploitation Linux dispose d’un ensemble de commandes permettant de
récupérer des informations sur les processus qui s’exécutent dans une machine Linux.
Il existe en particulier les commandes ps, pstree et top. Il existe également le pseudo-
système de fichiers /proc qui fournit une interface aux structures de données du kernel
et en particulier aux processus du système.
1.1 La commande ps
La commande ps permet d’afficher des informations sur une sélection de processus
actifs du système. La sélection des processus et les informations affichées dépendent
des options fournies à la commande. Sans options, ps sélectionne et affiche les processus
de l’utilisateur courant et associés au terminal dans lequel la commande est invoquée:
laloukil@laloukil:~$ ps
PID TTY TIME CMD
2636 pts/9 00:00:00 bash
2649 pts/9 00:00:00 ps
laloukil@laloukil:~$ ps -e
PID TTY TIME CMD
1 ? 00:00:01 init
2 ? 00:00:00 kthreadd
3 ? 00:00:00 ksoftirqd/0
5 ? 00:00:00 kworker/0:0H
7 ? 00:00:01 rcu_sched
8 ? 00:00:00 rcuos/0
9 ? 00:00:00 rcuos/1
10 ? 00:00:00 rcuos/2
11 ? 00:00:00 rcuos/3
12 ? 00:00:00 rcu_bh
.....
Les champs affichés pour chaque processus sont: PID (identifiant du processus), TTY
(le terminal associé au processus, ? indique que le processus n’est pas rattachée à un
terminal), TIME (temps CPU cumulé) et CMD (la commande qui a créé le processus).
3
1.1.2 Liste complète des processus
L’option -f (full) permet d’afficher des champs (colonnes) supplémentaires pour un
processus. Les colonnes additionnelles affichées par l’option -f sont UID (identifiant de
l’utilisateur), PPID (identifiant du processus père), C (rapport du temps d’utilisation
du CPU sur le temps d’exécution) et STIME (date de démarrage du processus).
L’option -f peut être combinée avec d’autres options pour afficher des colonnes addi-
tionnelles.
Exemple:
laloukil@laloukil:~$ ps -ef
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 19:24 ? 00:00:01 /sbin/init
root 2 0 0 19:24 ? 00:00:00 [kthreadd]
root 3 2 0 19:24 ? 00:00:00 [ksoftirqd/0]
root 5 2 0 19:24 ? 00:00:00 [kworker/0:0H]
root 7 2 0 19:24 ? 00:00:01 [rcu_sched]
root 8 2 0 19:24 ? 00:00:00 [rcuos/0]
root 9 2 0 19:24 ? 00:00:00 [rcuos/1]
root 10 2 0 19:24 ? 00:00:00 [rcuos/2]
root 11 2 0 19:24 ? 00:00:00 [rcuos/3]
.....
Remarque:
Il est parfois utile de relier en pipe la commande ps avec la commande more, less
ou grep pour prendre le temps de consulter les longues listes de processus (cas des
commandes more et less) ou pour filtrer le résultat de la commande ps et n’afficher
que les informations utiles (cas de la commande grep).
Exemples:
1. ps en pipe avec more affiche la liste des processus par page. Pour défiler la liste par
ligne (resp. par page), appuyer sur la touche Entree (resp. Barre d’espacement):
laloukil@laloukil:~$ ps -A | more
2. ps en pipe avec less permet de défiler la liste les processus vers le haut et vers
le bas à l’aide des touches Haut et Bas du clavier. Pour quitter la commande, il
suffit d’appuyer sur la touche Q:
laloukil@laloukil:~$ ps -A | less
4
laloukil@laloukil:~$ ps -A | grep firefox
12494 ? 00:01:20 firefox
laloukil@laloukil:~$ ps -f -u root
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 10:10 ? 00:00:01 /sbin/init
root 2 0 0 10:10 ? 00:00:00 [kthreadd]
root 3 2 0 10:10 ? 00:00:00 [ksoftirqd/0]
root 4 2 0 10:10 ? 00:00:00 [kworker/0:0]
.....
laloukil@laloukil:~$ ps -f -C getty
UID PID PPID C STIME TTY TIME CMD
root 901 1 0 10:10 tty4 00:00:00 /sbin/getty -8 38400 tty4
root 905 1 0 10:10 tty5 00:00:00 /sbin/getty -8 38400 tty5
root 911 1 0 10:10 tty2 00:00:00 /sbin/getty -8 38400 tty2
root 912 1 0 10:10 tty3 00:00:00 /sbin/getty -8 38400 tty3
root 915 1 0 10:10 tty6 00:00:00 /sbin/getty -8 38400 tty6
root 1059 1 0 10:10 tty1 00:00:00 /sbin/getty -8 38400 tty1
L’option -p filtre les processus par leur PID. La commande suivante affiche les processus
de PID 3564 et 3582:
laloukil@laloukil:~$ ps -f -p 3564,3582
UID PID PPID C STIME TTY TIME CMD
root 3564 2 0 11:14 ? 00:00:00 [kworker/1:0]
root 3582 2 0 11:23 ? 00:00:00 [kworker/0:2]
5
1.1.5 Tri des processus par utilisation du CPU et de la mémoire
Un administrateur système a souvent besoin de connaître les processus gourmands en
temps CPU et/ou en occupation de la mémoire. L’option --sort permet de trier la
liste des processus sur un champ donné ou un paramètre particulier.
Plusieurs champs peuvent être spécifiés avec l’option --sort. Les champs doivent être
dans ce cas séparés par une virgule. Les champs peuvent être préfixés par le signe "-"
ou "+" pour un tri descendant ou ascendant respectivement.
La commande suivante trie la liste de tous les processus par ordre décroissant sur
l’utilisation du CPU (colonne %CPU) puis par ordre décroissant sur l’occupation de la
mémoire (colonne %MEM).
6
Comme pour la commande ps, pstree peut être reliée en pipe avec les commandes
more, less ou grep pour consulter ou chercher un processus dans l’arborescence.
7
peuvent être configurés par l’utilisateur.
La commande fournit une interface interactive pour la manipulation des processus.
Elle permet de trier les processus par utilisation du CPU, utilisation de la mémoire,
etc. (voir manuel de la commande pour plus de détails).
Les informations affichées par la commande top sont organisées en deux zones: zone
de synthèse et zone des tâches.
La zone de synthèse donne des informations sur l’état général du système telles que
le nombre total de processus, le nombre de processus en exécution, nombre de processus
endormis, etc. Nous mettons l’accent sur la troisième, quatrième et cinquième ligne
qui donnent des informations respectivement sur l’utilisation du CPU, de la mémoire
physique et de la mémoire virtuelle.
• ut: temps passé à exécuter des processus utilisateurs (processus de basse priorité).
Sous Linux, l’échelle des priorités d’un processus varie de -20 (priorité la plus
élevée) à +19 (priorité la plus basse). Le niveau de priorité par défaut d’un
processus est celui de son processus parent, et vaut généralement zéro.
8
La quatrième ligne reflète la mémoire physique. Les champs affichés sont total, used
(mémoire utilisée incluant le cache disque), free (libre) et buffers (mémoire utilisée
pour les E/S).
La zone des tâches affiche une liste triée des processus en cours d’exécution sur votre
système. Par défaut, la liste est triée par ordre décroissant d’utilisation du processeur;
la liste peut cependant être triée sur d’autres champs tels que le taux d’occupation
de la mémoire, par exemple. Différents champs peuvent être affichés dans la zone des
tâches; les champs par défaut sont:
• VIRT: quantité totale de mémoire utilisée par le processus. Cela inclut le code,
les données et les librairies partagées utilisées.
• S: état du processus. Les valeurs les plus courantes sont S pour "Sleeping" ou R
pour "Running".
• %CPU: pourcentage du temps CPU utilisé par le processus. Ceci est relatif à un
seul processeur; dans un système multiprocesseur, il peut être supérieur à 100
• %MEM: pourcentage de RAM disponible utilisée par le processus. Ceci n’inclut pas
les données qui ont été échangées sur le disque.
• TIME+: temps CPU total utilisé par le processus depuis le début. Ce champs
compte uniquement le temps que le processus a utilisé le CPU et ne compte pas
le temps d’endormissement.
9
q Quit.
h ou ? Help.
s Fixer la durée entre les mises à jour de l’affichage.
espace Met à jour l’affichage.
M Trier les processus par taille de mémoire (colonne %MEM).
P Trier les processus par activité du CPU (colonne %CPU).
F ou O Sélectionner le champs sur lequel la liste des processus sera triée.
< > Déplacer le champs de trie: ’<’: champs gauche; ’>’: champs droit .
u Réduire l’affichage aux processus appartenant à un utilisateur spécifique.
k Tuer un processus.
Dans ce qui suit, nous allons décrire quelques fichiers et répertoires sous /proc. Pour
de plus amples détails, se référer aux pages du manuel de /proc.
10
1.5 Exercices
1. Utiliser la commande ps pour afficher les processus bash de tous les utilisateurs.
3. Utiliser les commandes ps et head pour afficher la liste des 5 processus les plus
gourmands en CPU.
4. Par défaut, la commande top n’affiche pas le champ PPID. Comment ajouter ce
champ?
7. Quelle commande Linux permet d’afficher la version de Linux installé sur votre
ordinateur?
11
2 Les fonctions systèmes getpid(), getppid()
Il existe dans le langage C deux fonctions systèmes qui retournent le PID du processus
courant (processus qui appelle la fonction) et le PID du père du processus courant.
Ces fonctions sont respectivement getpid() et getppid().
Synopsis
1 #i n c l u d e <s y s / t y p e s . h> // pour l e type pid_t
2 #i n c l u d e <u n i s t d . h> // pour l e s f o n c t i o n s g e t p i d ( ) e t g e t p p i d ( )
3
4 pid_t g e t p i d ( v o i d ) ;
5 pid_t g e t p p i d ( v o i d ) ;
Exemple 2.1
Le programme suivant récupère le PID du processus courant ainsi que le PID de son
père et les affiche à l’écran:
1 #i n c l u d e <s t d i o . h>
2 #i n c l u d e <u n i s t d . h>
3 #i n c l u d e <s y s / t y p e s . h>
4
5 i n t main ( ) {
6 pid_t my_pid ;
7 pid_t my_parent_pid ;
8 my_pid = g e t p i d ( ) ; // Retourne l e p i d du p r o c e s s u s a p p e l a n t
9 my_parent_pid = g e t p p i d ( ) ; // Retourne l e p i d du p e r e du p r o c e s s u s
appelant
10 p r i n t f ( "Le PID du p r o c e s s u s a p p e l a n t : %d\n" , my_pid ) ;
11 p r i n t f ( "Le PID du p e r e du p r o c e s s u s a p p e l a n t : %d\n" , my_parent_pid ) ;
12 sleep (100) ; // Endort l e p r o c e s s u s a p p e l a n t pendant 100 s e c
13 return 0;
14 }
Le processus fils est une copie exacte du processus père sauf sur les points suivants
(voir le manuel de fork() pour plus de détails):
12
• Le processus fils n’hérite pas des verrous mémoire du père.
• Les utilisations des ressources de processus et les compteurs de temps CPU sont
remises à zéro dans le fils.
En cas de succès, fork() retourne le PID du processus fils dans le processus père,
et la valeur 0 dans le processus fils. La valeur retournée par fork() permet donc au
programmeur de distinguer la partie de code exécutée par le processus père de celle
exécutée par le processus fils.
En cas d’échec, -1 est retourné dans le père et aucun processus fils n’est créé.
Exemple 3.1
Dans l’exemple suivant, le processus main crée un processus fils, affiche Hello, s’endort
pendant 100 secondes et se termine. Le processus fils créé par fork() affiche également
Hello, s’endort pendant 100 secondes et se termine. Vous allez constater que deux
Hello seront affichés à l’écran.
1 #i n c l u d e <s t d i o . h>
2 #i n c l u d e < s t d l i b . h>
3 #i n c l u d e <s y s / t y p e s . h>
4 #i n c l u d e <u n i s t d . h>
5
6 i n t main ( ) {
7 fork () ;
8 p r i n t f ( " H e l l o du p r o c e s s u s %d dont l e p e r e e s t %d\n" , g e t p i d ( ) , g e t p p i d
() ) ;
9 sleep (100) ;
10 return 0;
11 }
Exemple 3.2
Dans cet exemple, le processus main crée un processus fils, affiche son PID, s’endort
pendant 600 secondes, affiche un message de fin d’exécution et se termine. En cas
de succès de l’appel fork(), le processus fils affiche son PID, s’endort pendant 10
13
secondes, affiche un message de fin d’exécution et se termine. En cas d’échec de l’appel
de fork(), un message d’erreur est affiché et le programme se termine.
1 #i n c l u d e <s t d i o . h>
2 #i n c l u d e <s y s / t y p e s . h>
3 #i n c l u d e <u n i s t d . h>
4 #i n c l u d e < s t d l i b . h>
5
6 i n t main ( v o i d ) {
7 pid_t p i d ;
8 pid = f o r k ( ) ;
9 switch ( pid ) {
10 c a s e −1 : // e c h e c dans f o r k ( )
11 f p r i n t f ( s t d e r r , " e c h e c du f o r k . \ n" ) ;
12 exit (1) ;
13 break ;
14 case 0 : // p i d == 0 : p a r t i e du code e x e c u t e e par l e f i l s
15 f p r i n t f ( s t d o u t , " F i l s : j e demarre . Mon PID e s t %u . \ n" , g e t p i d ( ) ) ;
16 sleep (10) ;
17 f p r i n t f ( s t d o u t , " F i l s : j e t e r m i n e . \ n" ) ;
18 exit (0) ;
19 break ;
20 default : // p i d > 0 : p a r t i e du code e x e c u t e e par l e p r o c e s s u s p e r e
21 f p r i n t f ( s t d o u t , " Pere : j e demarre . Mon PID e s t %u . \ n" , g e t p i d ( ) ) ;
22 s l e e p ( 6 0 0 ) ; // S i m u l e r une e x e c u t i o n de 600 s e c . ∗/
23 f p r i n t f ( s t d o u t , " Pere : j e t e r m i n e . \ n" ) ;
24 exit (0) ;
25 break ;
26 }
27 return (0) ;
28 }
Synopsis:
1 #i n c l u d e <s y s / t y p e s . h>
2 #i n c l u d e <s y s / w a i t . h>
3
4 pid_t w a i t ( i n t ∗ s t a t u s ) ;
5 pid_t w a i t p i d ( pid_t pid , i n t ∗ s t a t u s , i n t o p t i o n s ) ;
Le processus qui appelle wait() est suspendu jusqu’à ce que l’un de ses fils se termine
ou jusqu’à ce qu’un signal à intercepter arrive. Lorsqu’un fils se termine, le processus
père est réveillé et la fonction retourne le PID de ce fils. Si, au moment de l’appel
14
wait(), un processus fils s’est déjà terminé (il est dans l’état zombie), la fonction re-
vient immédiatement et toutes les ressources utilisées par le fils sont libérées. Si ce
processus n’a pas de fils, la fonction retourne -1.
• 0 : attendre la fin de n’importe quel processus fils du même groupe que l’appelant.
Exemple 3.3
Dans cet exemple, le processus père crée un processus fils. Le processus fils affiche
"Fils: je demarre. Mon PID est ...", s’endort pendant une durée aléatoire entre 0 et
20 secondes puis affiche "Fils: je termine et je sors." et sort. Le processus père affiche
"Pere: J’attends la terminaison de mon fils ..." et attend que le fils termine auquel cas
il affiche "Pere: Mon fils ... s’est termine. Je sors." et sort.
1 #i n c l u d e <s t d i o . h>
2 #i n c l u d e <s y s / t y p e s . h> // pour l e type pid_t
3 #i n c l u d e <s y s / w a i t . h> // pour l ’ a p p e l de l a f o n c t i o n w a i t ( )
4 #i n c l u d e <u n i s t d . h> // pour l ’ a p p e l de l a f o n c t i o n g e t p i d ( )
5 #i n c l u d e < s t d l i b . h> // pour l ’ a p p e l de l a f o n c t i o n e x i t ( )
6
7 i n t main ( v o i d ) {
8 pid_t p i d ;
9 f p r i n t f ( s t d o u t , " Pere : j e demarre . Mon PID e s t %u . \ n" , g e t p i d ( ) ) ;
10 pid = f o r k ( ) ;
11 switch ( pid ) {
12 c a s e −1 : // e c h e c de f o r k ( )
13 f p r i n t f ( s t d e r r , " e c h e c du f o r k . \ n" ) ;
14 exit (1) ;
15
15 break ;
16 case 0 : // p i d == 0 : p a r t i e du code e x e c u t e e par l e f i l s
17 f p r i n t f ( s t d o u t , " F i l s : j e demarre . Mon PID e s t %u . \ n" , g e t p i d ( ) ) ;
18 s l e e p ( rand ( ) %20) ;
19 f p r i n t f ( s t d o u t , " F i l s : j e t e r m i n e e t j e s o r s . \ n" ) ;
20 exit (0) ;
21 break ;
22 default : // p i d > 0 : p a r t i e du code e x e c u t e e par l e p e r e
23 f p r i n t f ( s t d o u t , " Pere : J ’ a t t e n d s l a t e r m i n a i s o n de mon f i l s %u\n" ,
pid ) ;
24 w a i t (NULL) ;
25 f p r i n t f ( s t d o u t , " Pere : Mon f i l s %u s ’ e s t t e r m i n e . Je s o r s . \ n" , p i d
);
26 exit (0) ;
27 break ;
28 }
29 return (0) ;
30 }
Synopsis
1 #i n c l u d e <u n i s t d . h>
2
3 i n t e x e c l ( c o n s t c h a r ∗ path , c o n s t c h a r ∗ arg0 , . . . , c h a r ∗ argn ) ;
4 i n t e x e c v ( c o n s t c h a r ∗ path , c h a r ∗ c o n s t argv [ ] ) ;
Ces fonctions renvoient –1 en cas d’échec. Les fonctions execl() et execv() sont iden-
tiques et ne diffèrent que de la façon dont les arguments sont fournis à la fonction.
Pour execl(), les arguments sont fournis sous forme d’une liste terminée par le pointeur
NULL:
16
• arg0, arg1, ..., argn: les arguments du programme : arg0 reprend le nom du
programme. arg1, ..., argn-1 sont les arguments du programme et argn=NULL.
Pour la fonction execv(), les arguments sont fournis dans un vecteur de chaînes de
caractères dont le dernier argument est la valeur NULL :
Exemple 3.4
Dans cet exemple, le processus courant est remplacée par le processus ls. En cas de
succès de l’appel de execl(), c’est la commande ls -l qui sera exécutée. En cas
d’échec, c’est le message "Erreur lors de l’exécution de ls." qui sera affiché à l’écran.
1 #i n c l u d e <s t d i o . h>
2 #i n c l u d e <u n i s t d . h>
3
4 i n t main ( ) {
5 e x e c l ( "/ b i n / l s " , " l s " , "− l " , NULL) ;
6 p r i n t f ( " E r r e u r l o r s de l ’ e x e c u t i o n de l s . \n" ) ;
7 return 0;
8 }
Exemple 3.5
Le même exemple que précédemment avec l’utilisation de la fonction execv().
1 #i n c l u d e <s t d i o . h>
2 #i n c l u d e <u n i s t d . h>
3
4 #d e f i n e NMAX 5
5
6 i n t main ( ) {
7 c h a r ∗ argv [NMAX] ;
8 argv [ 0 ] = " l s " ;
9 argv [ 1 ] = "− l " ;
17
10 argv [ 2 ] = NULL;
11 e x e c v ( " / b i n / l s " , argv ) ;
12 p r i n t f ( " E r r e u r l o r s de l ’ e x e c u t i o n de l s . \n" ) ;
13 return 0;
14 }
3.4 Exercices
1. Considérons le programme C suivant (fork1.c):
1 #i n c l u d e <s t d i o . h>
2 #i n c l u d e < s t d l i b . h>
3 #i n c l u d e <s y s / t y p e s . h>
4 #i n c l u d e <u n i s t d . h>
5
6 i n t main ( ) {
7 pid_t p i d ;
8 p r i n t f ( " Je s u i s l e p r o c e s s u s %d\n" , g e t p i d ( ) ) ;
9 sleep (10) ;
10 p r i n t f ( " Appel de f o r k . \ n" ) ;
11 pid = f o r k ( ) ;
12 p r i n t f ( " f o r k ( ) r e t o u r n e %d\n" , p i d ) ;
13 sleep (10) ;
14 return 0;
15 }
1 #i n c l u d e <s t d i o . h>
2 #i n c l u d e < s t d l i b . h>
3 #i n c l u d e <s y s / t y p e s . h>
4 #i n c l u d e <u n i s t d . h>
5
6 int i ;
7 i n t main ( ) {
8 i = 1;
9 pid_t p i d ;
10 pid = getpid ( ) ;
11 p r i n t f ( "Avant f o r k : Je s u i s l e p r o c e s s u s main , mon p i d e s t = %d\n"
, pid ) ;
12 sleep (20) ;
18
13 p r i n t f ( " Appel de f o r k par l e p r o c e s s u s main de PID = %d . . . \n" ,
getpid () ) ;
14 p i d = f o r k ( ) ; // Clonage du p r o c e s s u s
15 switch ( pid ) {
16 c a s e −1: // p i d = −1: e r r e u r du f o r k ( )
17 f p r i n t f ( s t d e r r , " e r r e u r du f o r k . \ n" ) ;
18 exit (1) ;
19 break ;
20
21 case 0: // p i d = 0 : on e s t dans l e f i l s
22 f p r i n t f ( s t d o u t , " Fork r e u s s i . . . Je s u i s l e p r o c e s s u s f i l s c r e e
par f o r k : mon PID e s t = %d , l e PID de mon p e r e e s t = %d\n" ,
getpid () , getppid () ) ;
23 sleep (30) ;
24 f p r i n t f ( s t d o u t , " Fin du f i l s ( PID=%d ) , i=%d . . . \n" , g e t p i d ( ) ,
i);
25 exit (0) ;
26 break ;
27
28 default : // p i d > 0 : on e s t dans l e p e r e
29 // M o d i f i c a t i o n de l a v a r i a b l e i par l e p e r e
30 i = 2;
31 f p r i n t f ( s t d o u t , " Je s u i s l e p r o c e s s u s pere , j e v i e n s de
m o d i f i e r l a v a r i a b l e i , i= %d\n" , i ) ;
32 sleep (10) ;
33 p r i n t f ( " Fin du p e r e ( PID=%d ) , i = %d . . . \n" , g e t p i d ( ) , i ) ;
34 exit (0) ;
35 break ;
36 }
37 }
3. Écrire un programme C dans lequel le processus crée un fils, affiche son PID
et le PID du processus fils créé, s’endort pendant 30 secondes (sleep(30)) et
se termine (exit(0)). Le processus fils affiche son PID, appelle la fonction
executerQuelqueChose(), affiche son PID et se termine.
Le code de la fonction executerQuelqueChose() est donné ci-dessous:
1 #d e f i n e NB_ITERS 5
2
3 void executerQuelqueChose ( void ) {
4 f o r ( i n t i = 0 ; i <NB_ITERS ; i ++){
5 p r i n t f ( " i = %d\n" , i ) ;
6 s l e e p ( rand ( ) %4) ;
7 }
19
8 }
(a) hello1.c
1 #i n c l u d e <s t d i o . h>
2 #i n c l u d e <u n i s t d . h>
3
4 i n t main ( ) {
5 p r i n t f ( " H e l l o ! \ n" ) ;
6 sleep (30) ;
7 return 0;
8 }
(b) hello2.c
1 #i n c l u d e <s t d i o . h>
2 #i n c l u d e <u n i s t d . h>
3
4 i n t main ( ) {
5 p r i n t f ( " H e l l o ! \ n" ) ;
6 fork () ;
7 sleep (30) ;
8 return 0;
9 }
(c) hello3.c
1 #i n c l u d e <s t d i o . h>
2 #i n c l u d e <u n i s t d . h>
3
4 i n t main ( ) {
5 fork () ;
6 p r i n t f ( " H e l l o ! \ n" ) ;
7 sleep (30) ;
8 return 0;
9 }
20
(d) hello4.c
1 #i n c l u d e <s t d i o . h>
2 #i n c l u d e <u n i s t d . h>
3
4 i n t main ( ) {
5 fork () ;
6 fork () ;
7 p r i n t f ( " H e l l o ! \ n" ) ;
8 sleep (30) ;
9 return 0;
10 }
(e) hello5.c
1 #i n c l u d e <s t d i o . h>
2 #i n c l u d e <u n i s t d . h>
3
4 i n t main ( ) {
5 pid_t p i d ;
6 f o r ( i n t i = 1 ; i <= 2 ; ++i ) {
7 pid = f o r k ( ) ;
8 i f ( p i d != 0 )
9 p r i n t f ( " H e l l o ! \ n" ) ;
10 }
11 sleep (30) ;
12 return 0;
13 }
(f) hello6.c
1 #i n c l u d e <s t d i o . h>
2 #i n c l u d e <u n i s t d . h>
3
4 i n t main ( ) {
5 pid_t p i d ;
6 f o r ( i n t i = 1 ; i <= 2 ; ++i ) {
7 pid = f o r k ( ) ;
8 i f ( p i d == 0 )
9 break ; // Fin de l a b o u c l e f o r
10 else
11 p r i n t f ( " H e l l o ! \ n" ) ;
12 }
13 sleep (30) ;
14 return 0;
15 }
5. Écrire les programmes qui permettent de créer les arborescences des processus
schématisées ci-dessous. La variable entière n (initialisée dans le programme ou
21
lue à partir de la ligne de commande) représente le nombre de processus fils à
créer.
6. Écrire un programme C dans lequel le processus père crée deux processus fils et
s’endort pendant 10 secondes. Le 1er fils affiche les nombres paires entre 0 et 100,
le second fils affiche les nombres impaires entre 0 et 100. A chaque itération, le
processus fils affiche son PID, le PID de son père ainsi que le nombre.
7. Écrire un programme C dans lequel le processus main crée N processus fils (N étant
initialisé dans le programme ou lue à partir de la ligne de commande) puis attend
la terminaison de ses N processus fils avant de se terminer. A chaque terminaison
d’un fils, le processus père affiche son PID (le PID du fils terminé). Pour rappel,
la fonction wait() renvoie la valeur -1 quand il n’y a pas de processus fils en
exécution.
Chaque processus fils Pi affiche son PID et le PID de son père, s’endort pendant
une durée aléatoire (entre 0 et 10 secondes) et se termine (exit (i)).
Compiler le programme avec l’option -Wall.
8. Écrire un programme où le processus main crée 2 processus fils, l’un exécute le pro-
gramme /bin/ls avec l’option "-l -a", l’autre exécute le programme /bin/ps
avec l’option "-x", le processus main se termine immédiatement après la création
des 2 processus. Utiliser dans un premier temps la fonction execl().
22
1.
2.
3.
23
4 Le multithreading
4.1 Introduction
Les systèmes d’exploitations modernes tels que Windows, Unix, Linux ou MacOS sont
des systèmes multitâches. Ils peuvent exécuter plusieurs processus simultanément en
partageant les ressources de l’ordinateur (CPUs, mémoire principale, canaux d’E/S,
etc.) entre les différents processus. Le multitâche permet ainsi une meilleure exploita-
tion des ressources de l’ordinateur. Quand la machine est mono-processeur (ce qui est
rare de nos jours car les processeurs actuels disposent de plusieurs coeurs de calcul),
celui-ci est partagé entre les différents processus et, à tout instant, un seul processus
est exécuté à la fois. Dans les machines multi-processeurs, plusieurs processus peuvent
être exécutés en parallèle. Chaque processus en exécution possède son propre segment
de code, segment de données, ses registres, sa pile d’exécution, etc. (voir Fig. 1).
24
temps CPU pour effectuer des calculs, ce qui améliore la performance globale du pro-
gramme. Le multithreading permet aussi une meilleure interactivité du programme
avec les utilisateurs. Par exemple, dans un traitement de texte, pendant qu’un thread
fait l’impression ou l’enregistrement d’un fichier, un autre peut être utilisé pour con-
tinuer à lire les caractères tapés au clavier.
Termine l’exécution d’un thread et envoie une valeur de retour dans la variable
*retval.
Exemple 4.1
Dans le programme suivant, le processus main crée et démarre NUMTHREADS threads et
les joint avant de se terminer. Chaque thread exécute la fonction afficheHello() qui
reçoit en argument le rang du thread et affiche la chaîne "Hello World du thread
#x!" (x étant le rang du thread).
25
1 #i n c l u d e <s t d i o . h>
2 #i n c l u d e < s t d l i b . h>
3 #i n c l u d e <p t h r e a d . h>
4
5 #d e f i n e NUMTHREADS 5
6
1 #i n c l u d e <s t d i o . h>
2 #i n c l u d e < s t d l i b . h>
3 #i n c l u d e <p t h r e a d . h>
4
5 #d e f i n e NB_THREADS 3
6
7 s t r u c t thread_args {
8 i n t thread_id ;
9 c h a r ∗ message ;
10 };
11
12 void ∗ a f f i c h e H e l l o ( void ∗ threadarg ) {
13 s t r u c t t h r e a d _ a r g s ∗ th_args ;
14 th_args = ( s t r u c t t h r e a d _ a r g s ∗ ) t h r e a d a r g ;
15 p r i n t f ( " Thread ID : %d , Message : %s \n" , th_args−>thread_id , th_args−>
message ) ;
16 p t h r e a d _ e x i t (NULL) ;
17 }
18
26
19 i n t main ( ) {
20 pthread_t t h r e a d s [NB_THREADS ] ;
21 s t r u c t t h r e a d _ a r g s td [NB_THREADS ] ;
22 int rc ;
23 int i ;
24
25 f o r ( i =0; i < NB_THREADS; i ++){
26 p r i n t f ( "main ( ) : c r e a t i o n du t h r e a d %d\n" , i ) ;
27 td [ i ] . thread_id = i ;
28 td [ i ] . message = " C e c i e s t un message " ;
29 r c = p t h r e a d _ c r e a t e (& t h r e a d s [ i ] , NULL, a f f i c h e H e l l o , ( v o i d ∗ )&td [ i
]) ;
30 i f ( rc ){
31 p r i n t f ( " E r r e u r de c r e a t i o n du t h r e a d . \ n" ) ;
32 e x i t ( −1) ;
33 }
34 }
35 p t h r e a d _ e x i t (NULL) ;
36 }
27
4.3 Exercices
1. Écrire un programme C dans lequel le processus père (main) crée NbThreads
threads (la valeur de NbThreads est passée en ligne de commande ou initialisée
dans le programme), chaque thread exécute la fonction fonctionThread(). Le
processus main attend la fin de tous ses threads avant de se terminer.
La fonction exécutée par les threads (fonctionThread.c):
28
5 Les mutex et les sémaphores
5.1 Les mutex
Un mutex (MUTual EXclusion) est un structure utilisée pour assurer l’exclusion mutuelle
(accès non concurrent) pour l’accès aux sections critiques (parties de code qui modifient
des variables partagées entre plusieurs threads). Un mutex peut être dans deux états :
déverrouillé (pris par aucun thread) ou verrouillé (appartenant à un thread). Un mutex
ne peut être pris que par un seul thread à la fois. Un thread qui tente de verrouiller
un mutex déjà verrouillé est suspendu jusqu’à ce que le mutex soit déverrouillé.
1. Initialisation d’un objet mutex: la fonction pthread_mutex_init()
1 #i n c l u d e <p t h r e a d . h>
2
3 i n t pthread_mutex_init ( pthread_mutex_t ∗mutex , c o n s t
pthread_mutexattr_t ∗ mutex_attr ) ;
Initialise le mutex pointé par mutex selon les attributs spécifiés par mutex_attr.
Si mutex_attr vaut NULL, les paramètres par défaut sont utilisés. Une vari-
able mutex peut être initialisée de manière statique en utilisant la constante
PTHREAD_MUTEX_INITIALIZER.
29
4. Comment utiliser un mutex?
Soit x une ressource (une variable, un fichier, ...) partagée entre plusieurs threads.
Celle-ci peut être modifiée par plusieurs threads. x doit donc être protégée par
un mutex en entourant tous les accès en modification de x par la paire d’appels
pthread_mutex_lock() et pthread_mutex_unlock() comme indiqué ci-dessous:
1 int x ;
2 p t h r e a d \_mutex_t mut = PTHREAD_MUTEX_INITIALIZER;
3
4 pthread_mutex_lock(&mut ) ; // V e r r o u i l l a g e du mutex
5 ... ... ... ... ... ... . // S e c t i o n c r i t i q u e : p a r t i e de code q u i
6 ... ... ... ... ... ... . // m o d i f i e l a v a r i a b l e p a r t a g e e x
7 pthread_mutex_unlock(&mut ) ; // D e v e r r o u i l l a g e du mutex
5.2 Exemple
Dans cet exemple, le processus main crée et démarre 2 threads, thread1 et thread2,
thread1 (resp. thread2) incrémente (resp. décrémente) 100 000 fois la variable
partagée count. Le processus main joint les deux threads avant de se terminer.
1 #i n c l u d e <s t d i o . h>
2 #i n c l u d e < s t d l i b . h>
3 #i n c l u d e <p t h r e a d . h>
4
5 void ∗ incFonction ( ) ;
6 void ∗ decFonction ( ) ;
7
8 i n t count = 0 ; // v a r i a b l e p a r t a g e e e n t r e l e s t h r e a d s
9
10 i n t main ( ) {
11 s r a n d ( time ( 0 ) ) ;
12 i n t rc1 , r c 2 ;
13 pthread_t thread1 , t h r e a d 2 ;
14
15 // C r e a t i o n d e s 2 t h r e a d s
16 i f ( ( r c 1 = p t h r e a d _ c r e a t e (& thread1 , NULL, &i n c F o n c t i o n , NULL) ) ) {
17 p r i n t f ( " Echec de c r e a t i o n du Thread 1 : %d\n" , r c 1 ) ;
18 }
19
20 i f ( ( r c 2 = p t h r e a d _ c r e a t e ( &thread2 , NULL, &decFonction , NULL) ) ) {
21 p r i n t f ( " Echec de c r e a t i o n du Thread 2 : %d\n" , r c 2 ) ;
22 }
23
24 // Attendre que l e s t h r e a d s s e t e r m i n e n t
25 p t h r e a d _ j o i n ( thread1 , NULL) ;
30
26 p t h r e a d _ j o i n ( thread2 , NULL) ;
27 p r i n t f ( "Main −−> v a l e u r de count : %d\n" , count ) ;
28 exit (0) ;
29 }
30
31 void ∗ incFonction ( ) {
32 int i ;
33 f o r ( i = 0 ; i < 1 0 0 0 0 0 ; i ++) {
34 count ++;
35 }
36 pthread_exit (0) ;
37 }
38
39
40 void ∗ decFonction ( ) {
41 int i ;
42 f o r ( i = 0 ; i < 1 0 0 0 0 0 ; i ++) {
43 count −−;
44 }
45 pthread_exit (0) ;
46 }
1 #i n c l u d e <s t d i o . h>
2 #i n c l u d e < s t d l i b . h>
3 #i n c l u d e <p t h r e a d . h>
4
5 void ∗ incFonction ( ) ;
6 void ∗ decFonction ( ) ;
7
8 pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER;
9 i n t count = 0 ; // v a r i a b l e p a r t a g e e e n t r e l e s t h r e a d s
10
11 i n t main ( ) {
12 s r a n d ( time ( 0 ) ) ;
13 i n t rc1 , r c 2 ;
14 pthread_t thread1 , t h r e a d 2 ;
15
16 // C r e a t i o n d e s 2 t h r e a d s
17 i f ( ( r c 1 = p t h r e a d _ c r e a t e (& thread1 , NULL, &i n c F o n c t i o n , NULL) ) ) {
18 p r i n t f ( " Echec de c r e a t i o n du Thread 1 : %d\n" , r c 1 ) ;
19 }
20
21 i f ( ( r c 2 = p t h r e a d _ c r e a t e ( &thread2 , NULL, &decFonction , NULL) ) ) {
31
22 p r i n t f ( " Echec de c r e a t i o n du Thread 2 : %d\n" , r c 2 ) ;
23 }
24
25 // Attendre que l e s t h r e a d s s e t e r m i n e n t
26 p t h r e a d _ j o i n ( thread1 , NULL) ;
27 p t h r e a d _ j o i n ( thread2 , NULL) ;
28 p r i n t f ( "Main −−> v a l e u r de count : %d\n" , count ) ;
29 exit (0) ;
30 }
31
32 void ∗ incFonction ( ) {
33 int i ;
34 f o r ( i = 0 ; i < 1 0 0 0 0 0 ; i ++) {
35 pthread_mutex_lock ( &mutex1 ) ;
36 count ++;
37 pthread_mutex_unlock ( &mutex1 ) ;
38 }
39 pthread_exit (0) ;
40 }
41
42 void ∗ decFonction ( ) {
43 int i ;
44 f o r ( i = 0 ; i < 1 0 0 0 0 0 ; i ++) {
45 pthread_mutex_lock ( &mutex1 ) ;
46 count −−;
47 pthread_mutex_unlock ( &mutex1 ) ;
48 }
49 pthread_exit (0) ;
50 }
32
pshared initialisé à une valeur non nulle indique que le sémaphore est partagé
par les processus fils d’un processus.
2. Suspension d’un thread/processus: la fonction sem_wait()
1 #i n c l u d e <semaphore . h>
2
3 i n t sem_wait ( sem_t ∗sem ) ;
Suspend le thread appelant jusqu’à ce que le sémaphore pointé par sem ait une
valeur non nulle. Lorsque le compteur devient non nul, le compteur du sémaphore
est atomiquement décrémenté.
1 #i n c l u d e <semaphore . h>
2
C’est une variante non bloquante de sem_wait(). Si le sémaphore pointé par sem
est non nul, le compteur est décrémenté atomiquement et la fonction retourne 0.
Si le compteur du sémaphore est à 0, la fonction retourne EAGAIN.
3. Activation d’un thread/processus: la fonction sem_post()
1 #i n c l u d e <semaphore . h>
2
33
Détruit un sémaphore et libère toutes les ressources qu’il possède. Dans Linux-
Threads, on ne peut pas associer de ressource à un sémaphore donc cette fonction
ne fait que vérifier qu’aucun thread n’est bloqué sur le sémaphore.
5.4 Exemples
1. Le programme suivant crée et démarre 2 threads th1 et th2. th1 écrit dans le
tableau d’entiers tab, th2 lit le même tableau tab. Le sémaphore S initialisé à 0
empêche le thread th2 de lire tab avant que le thread th1 ne termine l’écriture
dans tab.
1 #i n c l u d e <s t d i o . h>
2 #i n c l u d e < s t d l i b . h>
3 #i n c l u d e <p t h r e a d . h>
4 #i n c l u d e <u n i s t d . h>
5 #i n c l u d e <semaphore . h>
6
7 #d e f i n e N 5
8 i n t tab [N ] ;
9 sem_t S ;
10
11 i n t main ( v o i d ) {
12 pthread_t th1 , th2 ;
13 void ∗ r e t ;
14
15 void ∗ lireTab ( void ∗ ) ;
16 void ∗ ecrireTab ( void ∗ ) ;
17 s r a n d ( time (NULL) ) ;
18
19 // I n i t i a l i s a t i o n de l a v a r i a b l e semaphore S
20 i f ( sem_init (&S , 0 , 0 ) ) {
21 perror (" seminit ") ;
22 e x i t (EXIT_FAILURE) ;
23 }
24
25 i f ( p t h r e a d _ c r e a t e (&th1 , NULL, e c r i r e T a b , NULL) < 0 ) {
26 p e r r o r ( " Thread e c r i r e ( p t h r e a d _ c r e a t e ) " ) ;
27 e x i t ( −1) ;
28 }
29
30 i f ( p t h r e a d _ c r e a t e (&th2 , NULL, l i r e T a b , NULL) < 0 ) {
31 p e r r o r ( " Thread l i r e ( p t h r e a d _ c r e a t e ) " ) ;
32 e x i t ( −1) ;
33 }
34
35 ( v o i d ) p t h r e a d _ j o i n ( th1 , &r e t ) ;
36 ( v o i d ) p t h r e a d _ j o i n ( th2 , &r e t ) ;
37
38 // D e s t r u c t i o n du semaphore S e t l i b e r a t i o n d e s r e s s o u r c e s
39 sem_destroy(&S ) ;
34
40
41 return 0;
42 }
43
2. Producteur-consommateur
1 #i n c l u d e <s t d i o . h>
2 #i n c l u d e < s t d l i b . h>
3 #i n c l u d e <u n i s t d . h>
4 #i n c l u d e <time . h>
5 #i n c l u d e <p t h r e a d . h>
6 #i n c l u d e <semaphore . h>
7
8 #d e f i n e SIZE 5 // t a i l l e du b u f f e r
9
10 i n t b u f f e r [ SIZE ] ; // b u f f e r d e s p r o d u i t s ( v a l e u r s )
11 i n t t a i l , head ; // i n d i c e s de t e t e e t de queue du b u f f e r
12
13 // semaphores r e p r e s e n t a n t r e s p e c t i v e m e n t l e nombre de p l a c e s l i b r e s
e t l e nombre de p r o d u i t s
14 sem_t empty , p r o d u c t s ;
15
16 v o i d ∗ p r o d u c e r ( v o i d ∗ a r g ) { // f o n c t i o n p r o d u c t e u r
17 int valeur ;
18 do { // p r o d u i r e une v a l e u r ( p r o d u i t ) j u s q u ’ a c e que l a v a l e u r e s t
0
19 i n t v a l e u r = rand ( ) % 1 0 0 ; // g e n e r e r une v a l e u r a l e a t o i r e e n t r e
0 e t 99
20 p r i n t f ( " p r o d u i t > %d\n" , v a l e u r ) ;
21 sem_wait(&empty ) ; // a t t e n d r e une p l a c e l i b r e dans l e b u f f e r
22 b u f f e r [ t a i l ] = v a l e u r ; // i n s e r e r une n o u v e l l e v a l e u r dans l e
buffer
35
23 t a i l = ( t a i l + 1 ) % SIZE ;
24 sem_post(& p r o d u c t s ) ; // i n c r e m e n t e r l e nombre de p r o d u i t s dans
le buffer
25 s l e e p ( rand ( ) % 5 ) ; // marquer une p o s e de d u r e e a l e a t o i r e ( e n t r e
0 et 4 s ) apres la production
26 }
27 while ( valeur ) ;
28 p t h r e a d _ e x i t (NULL) ; // t e r m i n e r l e t h r e a d p r o d u c t e u r
29 }
30
31 v o i d ∗ consumer ( v o i d ∗ a r g ) { // f o n c t i o n consommateur
32 int valeur ;
33 do { // consomme une v a l e u r ( p r o d u i t ) j u s q u ’ a c e que l a v a l e u r
consommee e s t 0
34 sem_wait(& p r o d u c t s ) ; // a t t e n d r e pour d e s p r o d u i t s dans l e
buffer
35 v a l e u r = b u f f e r [ head ] ; // p r e n d r e l a v a l e u r d ’ i n d i c e head dans
le buffer
36 head = ( head + 1 ) % SIZE ;
37 p r i n t f ( " \ tconsomme < %d\n" , v a l e u r ) ;
38 sem_post(&empty ) ; // i n c r e m e n t e r l e nombre de p l a c e s l i b r e s dans
le buffer
39 s l e e p ( rand ( ) % 5 ) ; // s i m u l e r l a consommation du p r o d u i t
40 } while ( valeur ) ;
41 p t h r e a d _ e x i t (NULL) ; // t e r m i n e r l e t h r e a d consommateur
42 }
43
5.5 Exercices
1. Écrire un programme multi-thread qui compte le nombre d’occurrences d’un élé-
ment dans un tableau d’entiers de grande taille. Le tableau de taille vectSize est
divisé en blocs de tailles égales, chacun des nbThreads threads démarrés compte
36
le nombre d’occurrences de l’élément dans le bloc qui lui est affecté et l’ajoute à
la variable partagée nbOccurrences initialisée à 0.
1 double r e s u l t = 0 . 0 ;
2
3 void produit_scalaire_seq ( double v [ ] , double u [ ] , i n t n) {
4 f o r ( i n t i = 0 ; i < n ; i ++) {
5 r e s u l t += v [ i ] ∗ u [ i ] ;
6 }
7 }
8
3. Dans l’exemple 5.2, l’écriture de tous les éléments de tab suivie de la lecture
de tous les éléments de tab. On souhaite maintenant que l’écriture de l’élément
tab[i] soit suivie de l’écriture de l’élément tab[i] pour i de 0 à n-1.
1 #i n c l u d e < s t d l i b . h>
2 #i n c l u d e <s t d i o . h>
3 #i n c l u d e <u n i s t d . h>
4 #i n c l u d e <s t r i n g . h>
5 #i n c l u d e <p t h r e a d . h>
6
7 v o i d mes1 ( v o i d ) {
8 s l e e p ( rand ( ) %5) ;
9 p r i n t f ( "Un , " ) ;
10 pthread_exit (0) ;
11 }
12
13 v o i d mes2 ( v o i d ) {
14 s l e e p ( rand ( ) %5) ;
15 p r i n t f ( "Deux , " ) ;
16 pthread_exit (0) ;
17 }
18
19 i n t main ( ) {
20 pthread_t th1 , th2 ;
21 s r a n d ( time (NULL) ) ;
37
22
23 i f ( p t h r e a d _ c r e a t e (&th1 , NULL, ( v o i d ∗ ) mes1 , NULL) ) {
24 f p r i n t f ( stderr , " thread1 " ) ;
25 e x i t (EXIT_FAILURE) ;
26 }
27
28 i f ( p t h r e a d _ c r e a t e (&th2 , NULL, ( v o i d ∗ ) mes2 , NULL) ) {
29 f p r i n t f ( stderr , " thread2 " ) ;
30 e x i t (EXIT_FAILURE) ;
31 }
32 p t h r e a d _ j o i n ( th1 ,NULL) ;
33 p t h r e a d _ j o i n ( th2 ,NULL) ;
34 p r i n t f ( " F i n i ! \ n" ) ;
35 e x i t (EXIT_SUCCESS) ;
36 }
1 #i n c l u d e < s t d l i b . h>
2 #i n c l u d e <s t d i o . h>
3 #i n c l u d e <u n i s t d . h>
4 #i n c l u d e <s t r i n g . h>
5 #i n c l u d e <semaphore . h>
6 #i n c l u d e <p t h r e a d . h>
7
8 void func1 ( void ) {
9 f o r ( i n t i = 0 ; i < 4 ; i ++){
10 s l e e p ( rand ( ) %5) ;
11 p r i n t f ( "One , " ) ;
12 }
13 pthread_exit (0) ;
14 }
15
16 void func2 ( void ) {
17 f o r ( i n t i = 0 ; i < 4 ; i ++){
18 s l e e p ( rand ( ) %5) ;
19 p r i n t f ( "Two , " ) ;
20 }
21 pthread_exit (0) ;
22 }
23
38
26 s l e e p ( rand ( ) %5) ;
27 p r i n t f ( " Three , " ) ;
28 }
29 pthread_exit (0) ;
30 }
31
32 void func4 ( void ) {
33 f o r ( i n t i = 0 ; i < 4 ; i ++){
34 s l e e p ( rand ( ) %5) ;
35 p r i n t f ( " Viva l ’ A l g e r i e . \ n" ) ;
36 }
37 pthread_exit (0) ;
38 }
39
40 i n t main ( ) {
41 s r a n d ( time (NULL) ) ;
42 pthread_t th1 , th2 , th3 , th4 ;
43
44 i f ( p t h r e a d _ c r e a t e (&th1 , NULL, ( v o i d ∗ ) func1 , NULL) ) {
45 f p r i n t f ( stderr , " thread1 " ) ;
46 e x i t (EXIT_FAILURE) ;
47 }
48
49 i f ( p t h r e a d _ c r e a t e (&th2 , NULL, ( v o i d ∗ ) func2 , NULL) ) {
50 f p r i n t f ( stderr , " thread2 " ) ;
51 e x i t (EXIT_FAILURE) ;
52 }
53
54 i f ( p t h r e a d _ c r e a t e (&th3 , NULL, ( v o i d ∗ ) func3 , NULL) ) {
55 f p r i n t f ( stderr , " thread3 " ) ;
56 e x i t (EXIT_FAILURE) ;
57 }
58
Utiliser les sémaphores pour qu’on ait tout le temps la sortie suivante : One,
Two, Three, Viva l’Algérie.
7. Soient 3 threads T1, T2 et T3 qui exécutent respectivement les fonctions affiche1(),
affiche2() et affiche3() suivantes (affiche123.c):
1 void ∗ a f f i c h e 1 ( ) {
39
2 f o r ( i n t i = 0 ; i < 1 0 ; i ++) {
3 p r i n t f ( "1" ) ;
4 }
5 pthread_exit (0) ;
6 }
7
8 void ∗ a f f i c h e 2 ( ) {
9 f o r ( i n t i = 0 ; i < 1 0 ; i ++) {
10 p r i n t f ( "2" ) ;
11 }
12 pthread_exit (0) ;
13 }
14
15 void ∗ a f f i c h e 3 ( ) {
16 f o r ( i n t i = 0 ; i < 1 0 ; i ++) {
17 p r i n t f ( "3" ) ;
18 }
19 pthread_exit (0) ;
20 }
40
6 Les variables de condition
6.1 Introduction
Les variables de condition fournissent un autre type de synchronisation entre threads
d’un processus.
Dans la norme POSIX, les variables de condition sont des variables de type pthread_cond_t.
Avant son utilisation dans un programme, une variable de condition doit être initialisée.
Il existe deux manières de le faire:
41
1 #i n c l u d e <p t h r e a d . h>
2
3 i n t pthread_cond_wait ( pthread_cond_t ∗ cond , pthread_mutex_t ∗mutex ) ;
Une variable condition est associée à un mutex pour éviter la situation de compétition
(race condition) créée par un thread qui se prépare à attendre et un autre qui doit
signaler la condition avant que le premier thread attende effectivement sur cette con-
dition ce qui peut conduire à une situation d’interblocage (deadlock). Le thread sera
en attente perpétuelle d’un signal qui n’arrivera peut être jamais.
1 #i n c l u d e <p t h r e a d . h>
2
3 i n t pthread_cond_signal ( pthread_cond_t ∗ cond ) ;
1 #i n c l u d e <p t h r e a d . h>
2
3 i n t pthread_cond_broadcast ( pthread_cond_t ∗ cond ) ;
1 #i n c l u d e <p t h r e a d . h>
2
3 i n t pthread_cond_destroy ( pthread_cond_t ∗ cond ) ;
42
Avant de terminer un programme, il ne faut pas oublier de détruire toutes les variables
condition utilisées dans le programme et libérer les ressources utilisées par celles-ci.
Ceci est réalisé par l’appel de la fonction pthread_cond_destroy(). Avant de détru-
ire une variable condition, il faut s’assurer qu’aucun thread n’est en attente sur cette
condition.
1 // Examiner l a c o n d i t i o n en t o u t e s e c u r i t e e t empecher l e s a u t r e s
2 // t h r e a d s de l a m o d i f i e r
3 pthread_mutex_lock (&mutex ) ;
4 w h i l e ( une c e r t a i n e c o n d i t i o n e s t f a u s s e )
5 pthread_cond_wait (&cond , &mutex ) ;
6
7 // F a i r e c e qu ’ i l y a l i e u de f a i r e quand l a c o n d i t i o n d e v i e n t v r a i e
8 faire_quelquechose () ;
9 pthread_mutex_unlock (&mutex ) ;
De l’autre coté, un thread qui signale une variable de condition, ressemble en général
à ceci:
1 // S ’ a s s u r e r d ’ abord qu ’ on a un a c c s e x c l u s i f a t o u t c e q u i comprend l a
v a r i a b l e de c o n d i t i o n
2 pthread_mutex_lock (&mutex ) ;
3
4 alterer_condition () ;
5
6 // R e v e i l l e r au moins un t h r e a d ( s ’ i l y en a ) q u i a t t e n d s u r l a c o n d i t i o n
7 pthread_cond_signal (&cond ) ;
8
9 // Pe rmettre aux a u t r e s t h r e a d s de p r o c e d e r
10 pthread_mutex_unlock (&mutex )
6.4 Exemple
Dans le programme suivant, le thread principal crée trois threads, threads[0], threads[1]
et threads[2]. threads[0] et threads[1] incrémentent la variable partagée "count",
threads[2] surveille la valeur de "count". Lorsque "count" atteint COUNT_LIMIT, le
thread en attente est signalé par l’un des deux threads qui incrémente. Le thread en
attente se réveille et modifie la valeur de count. Le programme continue jusqu’à ce
que les threads qui incrémentent atteignent TCOUNT. Le programme principal joint les
3 threads et affiche la valeur finale de count.
1 #i n c l u d e <p t h r e a d . h>
43
2 #i n c l u d e <s t d i o . h>
3 #i n c l u d e < s t d l i b . h>
4 #i n c l u d e <u n i s t d . h>
5
6 #d e f i n e NUM_THREADS 3
7 #d e f i n e TCOUNT 10
8 #d e f i n e COUNT_LIMIT 12
9
10 int count = 0 ;
11
12 pthread_mutex_t count_mutex ;
13 pthread_cond_t count_seuil_cond ;
14
15 v o i d ∗ inc_count ( v o i d ∗ t ) {
16 int i ;
17 l o n g my_id = ( l o n g ) t ;
18
19 f o r ( i =0; i < TCOUNT; i ++) {
20 pthread_mutex_lock(&count_mutex ) ;
21 count++;
22
23 // T e s t e r l a v a l e u r de count and s i g n a l e r l e t h r e a d en a t t e n t e s i l a
condition est remplie .
24 // Noter que c e c i s e p r o d u i t quand mutex e s t v e r r o u i l l e .
25
26 i f ( count == COUNT_LIMIT) {
27 p r i n t f ( " t h r e a d %l d ( inc_count ( ) ) : count = %d S e u i l a t t e i n t . " ,
my_id , count ) ;
28 pthread_cond_signal (& count_seuil_cond ) ;
29 p r i n t f ( " S i g n a l envoye . \ n" ) ;
30 }
31 p r i n t f ( " t h r e a d %l d ( inc_count ( ) ) : count = %d , D e v e r o u i l l a g e de mutex\
n" , my_id , count ) ;
32 pthread_mutex_unlock(&count_mutex ) ;
33
34 // S i m u l e r l ’ e x e c u t i o n d ’ une c e r t a i n e t a c h e
35 s l e e p ( rand ( ) %4) ;
36 }
37 p t h r e a d _ e x i t (NULL) ;
38 }
39
40 void ∗ surveille_count ( void ∗ t ) {
41 l o n g my_id = ( l o n g ) t ;
42
43 p r i n t f ( " t h r e a d %l d ( s u r v e i l l e _ c o u n t ( ) ) : Debut s u r v e i l l a n c e de count : \n
" , my_id ) ;
44
45 // V e r o u i l l a g e de mutex e t a t t e n t e s i l a c o n d i t i o n e s t non r e m p l i e (
count < COUNT_LIMIT.
46 // Noter que pthread_cond_wait ( ) va automatiquement e t atomiquement
d e v e r r o u i l l e r mutex pendant
47 // qu ’ i l a t t e n d . Noter e g a l e m e n t que s i COUNT_LIMIT e s t a t t e i n t e avant
l ’ a p p e l de
48 // pthread_cond_wait ( ) , l a b o u c l e e s t i g n o r e e pour empecher
44
pthread_cond_wait ( ) de ne j a m a i s r e t o u r n e r .
49
50 pthread_mutex_lock(&count_mutex ) ;
51 w h i l e ( count < COUNT_LIMIT) {
52 p r i n t f ( " t h r e a d %l d ( s u r v e i l l e _ c o u n t ( ) ) : Count= %d . S u s p e n s i o n de l ’
e x e c u t i o n e t a t t e n t e . . . \n" , my_id , count ) ;
53 pthread_cond_wait(&count_seuil_cond , &count_mutex ) ;
54 p r i n t f ( " t h r e a d %l d ( s u r v e i l l e _ c o u n t ( ) ) : S i g n a l r e c u . Count= %d\n" ,
my_id , count ) ;
55 p r i n t f ( " t h r e a d %l d ( s u r v e i l l e _ c o u n t ( ) ) : Maj de l a v a l e u r de count . . . \
n" , my_id ) ;
56 count += 1 2 5 ;
57 p r i n t f ( " t h r e a d %l d ( s u r v e i l l e _ c o u n t ( ) ) : maintenant , count = %d . \ n" ,
my_id , count ) ;
58 }
59 p r i n t f ( " t h r e a d %l d ( s u r v e i l l e _ c o u n t ( ) ) : D e v e r o u i l a g e de mutex . \ n" ,
my_id ) ;
60 pthread_mutex_unlock(&count_mutex ) ;
61 p t h r e a d _ e x i t (NULL) ;
62 }
63
82 // Attendre que t o u s l e s t h r e a d s t e r m i n e n t .
83 f o r ( i = 0 ; i < NUM_THREADS; i ++) {
84 p t h r e a d _ j o i n ( t h r e a d s [ i ] , NULL) ;
85 }
86 p r i n t f ( "Main ( ) : J o i n t u r e d e s %d t h r e a d s . Valeur f i n a l e de count = %d .
Termine . \ n" ,
87 NUM_THREADS, count ) ;
88
89 // N e t t o y e r e t s o r t i r
90 p t h r e a d _ a t t r _ d e s t r o y (& a t t r ) ;
91 pthread_mutex_destroy(&count_mutex ) ;
92 pthread_cond_destroy(& count_seuil_cond ) ;
93 p t h r e a d _ e x i t (NULL) ;
94 }
45
6.5 Exercices
1. Rdv de threads
2. Lecteurs/rédacteurs
3. Producteurs/consommateurs
46
7 Références bibliographiques
References
[ubu, ] http://manpages.ubuntu.com/manpages.
[tut, ] http://www.tutorialspoint.com/operating_system/os_multi_threading.htm.
[Blaess, 2005] Blaess, C. (2005). Programmation système en C sous Linux. Signaux, processus,
threads, IPC et sockets. Eyrolles.
[Silberschatz et al., 2004] Silberschatz, A., Galvin, P. B., and Gagne, G. (2004). Operating
System Concepts. John Wiley & Sons.
47