Vous êtes sur la page 1sur 17

Chapitre 4

Les threads
4.1 Introduction
Le modle de processus dcrit au chapitre prcdent est un programme
qui sexcute selon un chemin unique avec un seul compteur ordinal. On
dit quil a un flot de contrle unique ou un seul thread. De nombreux systmes dexploitation modernes offrent la possibilit dassocier un mme
processus plusieurs chemins dexcution ou multithread (figure 4.1). Ils
permettent ainsi lexcution simultane des parties dun mme processus.
Chaque partie correspond un chemin dexcution du processus. Le processus est vu comme tant un ensemble de ressources (code excutable,
segments de donnes, de fichiers, de priphriques, etc.) que ces parties appeles flots de contrle ou processus lgers (threads en anglais) partagent.
Chaque flot de contrle (thread) a cependant, en plus des ressources communes, sa propre zone de donnes ou de variables locales, sa propre pile
dexcution, ses propres registres et son propre compteur ordinal.

4.1.1 Avantages
Comparativement aux processus un flot de contrle unique, un thread
ou processus lger avec plusieurs flots de contrle prsente plusieurs avantages, notamment :
Ractivit. Le processus lger continue sexcuter mme si certaines
de ses parties sont bloques.
Partage de ressources.
conomie despace mmoire et de temps. Par exemple sous Solaris,
la cration dun processus est 30 fois plus lente que celle dun proces1

CHAPITRE 4. LES THREADS

un processus
un thread

multiples processus
un thread par processus

un processus
multiples threads

multiples processus
multiples threads par processus

F IG . 4.1 Threads vs. processus.


sus thread.

4.1.2 Threads utilisateur et noyau


La majorit des systmes permettent le multiflot (multithreading ). Ils
sont offerts soit au niveau utilisateur, soit au niveau noyau (voir le Chapitre
?? Ordonnancement des processus, pour les dtails sur lordonnancement
des threads et des processus).
Les threads utilisateur sont supports au-dessus du noyau et sont implants par une bibliothque de threads au niveau utilisateur (par exemple
pthread sous Linux ou thread dans Solaris). Ils sont portables sur
diffrentes plate-formes. Ils sont grs par une application o le blocage du thread peut bloquer le processus complet. Le changement de
contexte est rapide.
Les threads noyau sont directement supports par le noyau du systme
dexploitation. Le systme dexploitation se charge de leur gestion et
le changement de contexte est lent.
Les threads combins sont implants par le systme dexploitation (utilisateur et systme). Les threads utilisateur sont associs des threads

4.2. SERVICES POSIX DE GESTION DE THREADS

systme. La plupart des tches gestion seffectuent sous le mode utilisateur.


Les oprations concernant les threads sont notamment la cration, la terminaison, la suspension et la relance.

4.2 Services Posix de gestion de threads


Linux ne fait pas de distinction entre les processus et les threads. Un
thread est un processus qui partage un certain nombre de ressources avec
le processus crateur : lespace dadressage, les fichiers ouverts ou autres.
Pour la gestion Posix de threads, Linux utilise la bibliothque pthread,
qui doit tre appele par lditeur de liens.
Cration de threads
int pthread_create (pthread_t *thread ,
pthread_attr_t *attr,
void *nomfonction,
void *arg );

Le service pthread_create() cre un processus lger qui excute la fonction nomfonction avec largument arg et les attributs attr. Les attributs
permettent de spcifier la taille de la pile, la priorit, la politique de planification, etc. Il y a plusieurs formes de modification des attributs.
Suspension de threads
int pthread_join(pthread_t *thid,

void **valeur_de_retour);

pthread_join() suspend lexcution dun processus lger jusqu ce que


termine le processus lger avec lidentificateur thid. Il retourne ltat de
terminaison du processus lger.
Terminaison de threads
void pthread_exit(void *valeur_de_retour);

pthread_exit() permet un processus lger de terminer son excution,


en retournant ltat de terminaison.
int pthread_attr_setdetachstate(pthread_attr_t *attr,
int detachstate);

CHAPITRE 4. LES THREADS

pthread_attr_setdetachstate() sert tablir ltat de terminaison


dun processus lger :
Si detachstate = PTHREAD_CREATE_DETACHED le processus lger librera ses ressources quand il terminera.
Si detachstate = PTHREAD_CREATE_JOINABLE le processus lger ne librera pas ses ressources. Il sera donc ncessaire dappeler
phtread_join().
Exemple 1. Le programme thread-pid.c montre limplantation de
threads dans GNU/Linux, ainsi que la rcupration du pid du thread.

Listing 4.1 thread-pid.c


# include < unistd . h>
# i n c l u d e < pthread . h >
# i n c l u d e < s t d i o . h>

// pour s l e e p
// p t h r e a d _ c r e a t e , p t h r e a d _ j o i n , p t h r e a d _ e x i t

void f o n c t i o n ( void arg )


{
p r i n t f ( " pid du t h r e a d f i l s = %d\n " , ( i n t ) g e t p i d ( ) ) ;
10

while ( 1 ) ; // f o r e v e r
r e t u r n NULL;

i n t main ( )
{
pthread_t thread ;
p r i n t f ( " pid de main = %d\n " , ( i n t ) g e t p i d ( ) ) ;
p t h r e a d _ c r e a t e (& t h r e a d , NULL, & f o n c t i o n , NULL) ;
while ( 1 ) ; // f o r e v e r

20

return 0 ;

tant donn que aussi bien main() que la fonction_thread() tournent


tout le temps, cela nous permet de regarder de prs des dtails de leur
excution. Le programme doit tre compil avec la librairie -lpthread
Lexcution en tche de fond de pthread-pid.c est :
leibnitz> gcc -o pthread-pid thread-pid.c -lpthread
leibnitz> pthread-pid &
[1] 24133
leibnitz> pid de main = 24133
pid du thread fils = 24136

4.2. SERVICES POSIX DE GESTION DE THREADS


leibnitz> ps x
PID TTY
STAT
23928 pts/2
S
24133 pts/2
R
24135 pts/2
S
24136 pts/2
R
24194 pts/2
R

TIME COMMAND
0:00 -tcsh
0:53 pthread-pid
0:00 pthread-pid
0:53 pthread-pid
0:00 ps x

Le pid 24133 correspond au processus de main(), 24136 est le PID du


fonction_thread() elle mme, mais qui est le processus 24135 qui
en plus dort ? Dun autre cot, la seule faon de terminer lexcution du
programme prcdent, cest lutilisation de la commande kill, avec le pid
correspondant :
leibnitz> kill 24133
leibnitz> [1]
Terminated

pthread-pid

F IG . 4.2 Exemple de synchronisation de threads.


Exemple 2. La figure 4.2 illustre un exemple de cration de threads. Le
thread principal cre deux threads A et B et attend la fin de leurs excutions. Un de ces threads cre lui-mme un thread C qul attendra avant de

CHAPITRE 4. LES THREADS

terminer. Le listing exemple-threads.c montre une implmentation de


cet exemple.

Listing 4.2 exemple-threads.c


# define _REENTRANT
# include < pthread . h>
# include < u n i s t d . h>
# include < s t d i o . h>

10

void a f f i c h e r ( i n t n , char l e t t r e )
{
int i , j ;
f o r ( j = 1 ; j <n ; j + + ) {
for ( i = 1 ; i < 1 0 0 0 0 0 0 0 ; i ++);
p r i n t f ( "%c " , l e t t r e ) ;
f f l u s h ( stdout ) ;
}
}

20

threadA ( void

void
{

inutilise )

a f f i c h e r ( 1 0 0 , A ) ;
p r i n t f ( "\n Fin du t h r e a d A\n " ) ;
f f l u s h ( stdout ) ;
p t h r e a d _ e x i t (NULL) ;
30

void threadC ( void i n u t i l i s e )


{
a f f i c h e r ( 1 5 0 , C ) ;
p r i n t f ( "\n Fin du t h r e a d C\n " ) ;
f f l u s h ( stdout ) ;
40

p t h r e a d _ e x i t (NULL) ;

void

threadB ( void

inutilise )

4.2. SERVICES POSIX DE GESTION DE THREADS

{
p t h r e a d _ t thC ;
p t h r e a d _ c r e a t e (&thC , NULL , threadC , NULL) ;
50

afficher (100,B ) ;
p r i n t f ( "\n Le t h r e a d B a t t e n d l a f i n du t h r e a d C\n " ) ;
p t h r e a d _ j o i n ( thC ,NULL) ;
p r i n t f ( "\n Fin du t h r e a d B\n " ) ;
f f l u s h ( stdout ) ;
p t h r e a d _ e x i t (NULL) ;

60

}
i n t main ( )
{
int i ;
p t h r e a d _ t thA , thB ;
p r i n t f ( " C r e a t i o n du t h r e a d A" ) ;
p t h r e a d _ c r e a t e (&thA , NULL , threadA , NULL) ;
p t h r e a d _ c r e a t e (& thB , NULL , threadB , NULL) ;
sleep ( 1 ) ;

70

// a t t e n d r e que l e s t h r e a d s a i e n t termine
p r i n t f ( " Le t h r e a d p r i n c i p a l a t t e n d que l e s a u t r e s se t e r m i n e n t \n " ) ;
p t h r e a d _ j o i n ( thA ,NULL) ;
p t h r e a d _ j o i n ( thB ,NULL) ;
80

exit (0);

Exemple 3. Le programme threads.cpp montre lutilisation des variables globales, ainsi que le passage de paramtres lors des appels de threads.
Listing 4.3 threads.cpp
# include < u n i s t d . h >
# include < pthread . h >

// s l e e p
// p t h r e a d _ c r e a t e , p t h r e a d _ j o i n , p t h r e a d _ e x i t

CHAPITRE 4. LES THREADS

8
# include < s t d i o . h>

i n t glob = 0 ;
void increment ( void

) ; void

decrement ( void

);

10

i n t main ( )
{
pthread_t tid1 , tid2 ;
p r i n t f ( " i c i main[%d ] , glob = %d\n " , g e t p i d ( ) , glob ) ;
// c r a t i o n d un t h r e a d pour increment
i f ( p t h r e a d _ c r e a t e (& t i d 1 , NULL , increment , NULL ) ! = 0 )
return
1;
p r i n t f ( " i c i main : c r e a t i o n du t h r e a d[%d ] avec s u c c s \n " , t i d 1 ) ;

// c r a t i o n d un t h r e a d pour decrement
i f ( p t h r e a d _ c r e a t e (& t i d 2 , NULL , decrement , NULL ) ! = 0 )
return
1;
p r i n t f ( " i c i main : c r e a t i o n du t h r e a d [%d ] avec s u c c s \n " , t i d 2 ) ;

20

}
30

// a t t e n d r e l a f i n des t h r e a d s
p t h r e a d _ j o i n ( t i d 1 ,NULL) ;
p t h r e a d _ j o i n ( t i d 2 ,NULL) ;
p r i n t f ( " i c i main : f i n des t h r e a d s , glob = %d \ n " , glob ) ;
return 0 ;

decrement ( void

void
{

i n t dec = 1 ;
sleep ( 1 ) ;
glob = glob
dec ;
p r i n t f ( " i c i decrement [%d ] , glob = %d\n " , p t h r e a d _ s e l f ( ) , glob ) ;
p t h r e a d _ e x i t (NULL) ;

}
void
{

40

increment ( void

int inc =2;


sleep ( 1 0 ) ;
glob=glob + i n c ;
p r i n t f ( " i c i increment [%d ] , glob = %d\n " , p t h r e a d _ s e l f ( ) , glob ) ;
p t h r e a d _ e x i t (NULL) ;

Rsultat du programme threads.cpp :


leibnitz> gcc -o pthread threads.cpp -lpthread

4.2. SERVICES POSIX DE GESTION DE THREADS

leibnitz> pthread
ici main[21939], glob = 0
ici main : creation du thread[1026] avec succs
ici main : creation du thread [2051] avec succs
ici increment[21941], glob = 2
ici decrement[21942], glob = 1
ici main : fin des threads, glob = 1
leibnitz>

Le programme thread-param.c suivant illustre le passage de paramtres entre threads.


Exemple 4. Passage de paramtres.

Listing 4.4 thread-param.c


# include
# include
# include
# include

< s t d i o . h>
< u n i s t d . h>
< s t d l i b . h>
< pthread . h>

void t h r e a d _ f u n c t i o n ( void arg ) ;


char message [ ] = " Hola mundo" ;
10

i n t main ( )
{
int res ;
pthread_t a_thread ;
void t h r e a d _ r e s u l t ;

20

30

r e s = p t h r e a d _ c r e a t e (& a _ t h r e a d , NULL , t h r e a d _ f u n c t i o n ,
( void ) message ) ;
i f ( res ! = 0 )
{
p e r r o r ( " Thread c r e a t i o n f a i l e d " ) ;
e x i t ( EXIT_FAILURE ) ;
}
p r i n t f ( " En a t t e n t e que l e t h r e a d termine . . . \ n " ) ;
res = pthread_join ( a_thread , & thread_result ) ;
i f ( res ! = 0 )
{
p e r r o r ( " Echec dans l union du t h r e a d " ) ;
e x i t ( EXIT_FAILURE ) ;
}
p r i n t f ( " Thread termine % s\n " , ( char ) t h r e a d _ r e s u l t ) ;
p r i n t f ( " Le message e s t % s\n " , message ) ;
e x i t ( EXIT_SUCCESS ) ;

CHAPITRE 4. LES THREADS

10
}
void
{

40

t h r e a d _ f u n c t i o n ( void

arg )

p r i n t f ( " La f o n c t i o n du t h r e a d demarre . L argument e s t % s \ n " ,


( char ) arg ) ;
sleep ( 5 ) ;
s t r c p y ( message , " Adios ! " ) ;
p t h r e a d _ e x i t ( " Merci par l e temps de CPU" ) ;

Rsultat du programme thread-param.c :


leibnitz> gcc -o thread-param thread-param.c -lpthread
leibnitz> thread-param
leibnitz> Le thread demarre. Largument est Hola mundo
En attente que le thread termine...
Thread termin Merci pour le temps de lUCT
Le message est Adios!
leibnitz>

Algorithmes vectoriels
Il y a plusieurs exemples dutilisation des threads pour le traitement
parallle de linformation. Un serveur de fichiers 1 par exemple, ncessite
la gestion et la manipulation de plusieurs requtes en mme temps, qui
peuvent tre effectues par lutilisation des threads. Ainsi, la cration dune
thread chaque requte est envisageable : les threads effectuent les traitements ncessaires et envoient les rsultats au client.
La manipulation des matrices et des vecteurs est un autre champ particulirement propice pour les algorithmes parallles. La multiplication de
deux matrices a et b de colonnes = lignes = 3, par exemple, peut tre
paralllis, puisque :




       

   !  $#
   
" !
   
" !

  "  &%' (  &%)  " *   " +%' ( ,+%-  "!   " .%-  +%-  " 
 "  &%' / &%) " *  " +%' /,+%- "!  " .%- +%- "
   %'    %)       %'    %-       %-    %-   
1

Le modle client-serveur sera tudi au Chapitre ?? Introduction aux systmes distribus.

4.2. SERVICES POSIX DE GESTION DE THREADS

11

Ainsi, chaque lment de la matrice rsultat peut tre un thread. On


aura donc 9 thread dans cet exemple.
Exemple 5.
Le programme suma-mat.c ci-dessous illustre lutilisation des vecteurs de threads pour faire la somme de deux matrices A et B de N lments.
La somme de chaque ligne est calcule avec des threads indpendants en
parallle. Les composantes sont gnres alatoirement avec srand().

# i n c l u d e < pthread . h>


# i n c l u d e < s t d i o . h>
# i n c l u d e < s t d l i b . h>
# i n c l u d e < time . h>
# define N 4
# define M 10

10

20

Listing 4.5 suma-mat.c

// T a i l l e des m a t r i c e s
// Nb Maximum

void s o m m e _ v e c t o r i e l l e ( void arg ) ;


// Somme des m a t r i c e s
void imprime ( i n t m a t r i c e [N] [N ] ) ;
p t h r e a d _ t t i d [N ] ;
// V e c t e u r s t h r e a d s
i n t A[N] [N ] , B [N] [N ] ;
// M a t r i c e s
i n t C[N] [N ] ;
// C=A+B
int f i l a ;
// La f i l e des 2 m a t r i c e s
i n t main ( void )
{
int j , k , semilla ;
// Generation des m a t r i c e s
s e m i l l a = time (NULL ) ;
srand ( s e m i l l a ) ;
f o r ( j = 0 ; j <=N 1 ; j ++)
f o r ( k = 0 ; k<=N 1 ; k++)
{
A[ j ] [ k ] = rand ( ) % (M) ; // a l e a t o i r e 0 M
B [ j ] [ k ] = rand ( ) % (M) ;
}
f o r ( j = 0 ; j <=N 1 ; j ++)
{
p r i n t f ( " Thread f i l e %d\n " , j ) ;
p t h r e a d _ c r e a t e (& t i d [ j ] , NULL , s o m m e _ v e c t o r i e l l e , ( void
}
f o r ( j = 0 ; j <=N 1 ; j ++)
{
p r i n t f ( " Attendre t h r e a d %d\n " , j ) ;
p t h r e a d _ j o i n ( t i d [ j ] , NULL) ;
}
p r i n t f ( "\n\nMatrice A" ) ;



30

) j );

CHAPITRE 4. LES THREADS

12

40

imprime (A ) ;
p r i n t f ( "\n\nMatrice B " ) ;
imprime ( B ) ;
p r i n t f ( "\n\n Matriz C=A+B " ) ;
imprime (C ) ;
return 0 ;

void imprime ( i n t m a t r i c e [N] [N] )


{
int j , k ;
f o r ( j = 0 ; j <=N 1 ; j ++)
{
p r i n t f ( "\n " ) ;
f o r ( k = 0 ; k<=N 1 ; k++)
p r i n t f ( "%d \ t " , m a t r i c e [ j ] [ k ] ) ;
}
}

50

60

void s o m m e _ v e c t o r i e l l e ( void arg )


{
int f , k ;
f = ( i n t ) arg ;
p r i n t f ( "Somme de f i l e %d\n " , f ) ;
f o r ( k = 0 ; k<=N 1 ; k++)
C[ f ] [ k ] = A[ f ] [ k]+B [ f ] [ k ] ;

p t h r e a d _ e x i t (NULL ) ;

Excution du programme suma-mat.c :


leibnitz> suma-mat
Thread file 0
Thread file 1
Thread file 2
Somme de file 0
Somme de file 2
Thread file 3
Somme de file 3
Attendre thread 0
Attendre thread 1
Somme de file 1
Attendre thread 2
Attendre thread 3
Matrice A

4.2. SERVICES POSIX DE GESTION DE THREADS


6
5
2
4

6
5
7
1

0
7
0
6

9
7
6
5

Matrice
0
9
6
4

B
3
3
8
8

9
5
6
2

7
6
4
3

9
12
6
8

16
13
10
8

Matriz C=A+B
6
9
14
8
8
15
8
9

13

leibnitz>

Exemple 6. Le programme threads.cpp montre lutilisation des variables globales et le passage de paramtres.

# include < u n i s t d . h >


# include < pthread . h >
# include < s t d i o . h>

i n t glob = 0 ;
void increment ( void

10

Listing 4.6 threads.cpp

// s l e e p
// p t h r e a d _ c r e a t e , p t h r e a d _ j o i n , p t h r e a d _ e x i t

) ; void

decrement ( void

);

i n t main ( )
{
pthread_t tid1 , tid2 ;
p r i n t f ( " i c i main[%d ] , glob = %d\n " , g e t p i d ( ) , glob ) ;
// c r a t i o n d un t h r e a d pour increment
i f ( p t h r e a d _ c r e a t e (& t i d 1 , NULL , increment , NULL) ! = 0 )
return
1;
p r i n t f ( " i c i main : c r e a t i o n du t h r e a d[%d ] avec s u c c s \n " , t i d 1 ) ;

20

// c r a t i o n d un t h r e a d pour decrement
i f ( p t h r e a d _ c r e a t e (& t i d 2 , NULL , decrement , NULL) ! = 0 )
return
1;
p r i n t f ( " i c i main : c r e a t i o n du t h r e a d [%d ] avec s u c c s \n " , t i d 2 ) ;

// a t t e n d r e l a f i n des t h r e a d s
p t h r e a d _ j o i n ( t i d 1 ,NULL) ;

CHAPITRE 4. LES THREADS

14

}
30

p t h r e a d _ j o i n ( t i d 2 ,NULL) ;
p r i n t f ( " i c i main : f i n des t h r e a d s , glob = %d \ n " , glob ) ;
return 0 ;

decrement ( void

void
{

i n t dec = 1 ;
sleep ( 1 ) ;
glob = glob
dec ;
p r i n t f ( " i c i decrement [%d ] , glob = %d\n " , p t h r e a d _ s e l f ( ) , glob ) ;
p t h r e a d _ e x i t (NULL) ;

}
void
{

40

increment ( void

int inc =2;


sleep ( 1 0 ) ;
glob=glob + i n c ;
p r i n t f ( " i c i increment [%d ] , glob = %d\n " , p t h r e a d _ s e l f ( ) , glob ) ;
p t h r e a d _ e x i t (NULL) ;

Trois excutions du programme threads.cpp, sur la machine Unix jupiter :


jupiter% gcc -o threads threads.cpp -lthread
jupiter% threads
ici main[18745], glob = 0
ici increment[18745], glob = 2
ici decrement[18745], glob = 1
ici main, fin des threads, glob = 1
jupiter% threads
ici main[18751], glob = 0
ici decrement[18751], glob = -1
ici increment[18751], glob = 1
ici main, fin des threads, glob = 1
jupiter% threads
ici main[18760], glob = 0
ici increment[18760], glob = 1
ici decrement[18760], glob = 1
ici main, fin des threads, glob = 1

Exemple 7.

4.2. SERVICES POSIX DE GESTION DE THREADS

15

Le programme threads2.cpp effectue le passage de paramtres un


thread :

Listing 4.7 threads2.cpp


# i n c l u d e < u n i s t d . h > //pour s l e e p
# i n c l u d e < t h r e a d . h > // t h r _ c r e a t e e t t h r _ j o i n
# i n c l u d e < s t d i o . h>





i n t glob = 0 ;
void increment ( void
void decrement ( void

inc ) ;
dec ) ;

i n t main ( )
{
i n t i n c = 2 , dec = 1 ;
p r i n t f ( " i c i main[%d ] , glob = %d\n " , g e t p i d ( ) , glob ) ;
t h r _ c r e a t e (NULL, 0 , increment , & i n c , 0 , NULL) ;
t h r _ c r e a t e (NULL, 0 , decrement , & dec , 0 , NULL) ;
while ( ( t h r _ j o i n (NULL,NULL,NULL) = = 0 ) ) ;
p r i n t f ( " i c i main , f i n des t h r e a d s , glob = %d \ n " , glob ) ;
return 0 ;
}

10

20

void decrement ( void dec )


{
int locd = ( int
) dec ;
sleep ( 1 ) ;
glob = l o c d ;
p r i n t f ( " i c i decrement [%d ] , glob = %d\n " , g e t p i d ( ) , glob ) ;
r e t u r n ( NULL) ;
}

30

void increment ( void i n c )


{
int loci = ( int
) inc ;
sleep ( 1 ) ;
glob + = l o c i ;
p r i n t f ( " i c i increment [%d ] , glob = %d\n " , g e t p i d ( ) , glob ) ;
r e t u r n ( NULL) ;
}

Excution programme threads2.cpp :


jupiter% gcc threads2.cpp -lthread
jupiter% a.out
ici main[19451], glob = 0
ici decrement[19451], glob = 1

16

CHAPITRE 4. LES THREADS

ici increment[19451], glob = 2


ici main, fin des threads, glob = 1

4.3. EXERCICES

17

4.3 Exercices
1. On sait quun thread est cr par un processus parent, mais quelle
diffrence existe-il entre un thread et un processus fils ?
2. Quelle serait un avantage principal utiliser des threads plutt que
des processus fils ?