Vous êtes sur la page 1sur 51

Chapitre 6

Synchronisation des processus


sieurs processus sexcutent en pseudo-parallle ou en parallle et partagent des objets (mmoires, imprimantes, etc.). Le partage dobjets sans prcaution particulire peut conduire des rsultats imprvisibles. La solution au problme sappelle synchronisation des processus.

Ans un systme dexploitation multiprogramm en temps partag, plu-

6.1 Introduction
Considrez par exemple, le fonctionnement dun spool dimpression montr sur la gure 6.1, qui peut tre dlicat. Processus A Rpertoire de spoole Processus B F IG . 6.1 Spool dune imprimante. Quand un processus veut imprimer un chier, il doit placer le nom de ce chier dans un rpertoire spcial, appel rpertoire de spool. Un autre processus, le dmon dimpression, vrie priodiquement sil y a des chiers imprimer. Si cest le cas, il les imprime et retire leur nom du rpertoire. Supposons que : Le rpertoire dimpression ait un trs grand nombre demplacements, numrots 0,1,2,...,N. Chaque emplacement peut contenir le nom dun chier imprimer. 1 Dmon

CHAPITRE 6. SYNCHRONISATION DES PROCESSUS

Tous les processus partagent la variable in qui pointe sur le prochain emplacement libre du rpertoire. Deux processus A et B veulent placer chacun un chier dans la le dimpression et que la valeur de in est 7. Le processus A place son chier la position in qui vaut 7. Une interruption dhorloge arrive immdiatement aprs et le processus A est suspendu pour laisser place au processus B. Ce dernier place galement son chier la position in qui vaut toujours 7 et met jour in qui prend la valeur 8. Il efface ainsi le nom du chier plac par le processus A. Lorsque le processus A sera relanc, il met jour la valeur de in qui prend la valeur 9. Sous ces conditions, deux problmes peuvent se prsenter : Le chier du processus A ne sera jamais imprim ; in ne pointe plus sur le prochain emplacement libre. Comment viter ces problmes ? le problme provient du fait que le processus B a utilis une variable partage avant que A ait ni de sen servir. Si lon arrive bien synchroniser les processus, le problme peut tre rsolu. Lorsquun processus accde la variable in, les autres processus ne doivent ni la lire, ni la modier jusqu ce que le processus ait termin de la mettre jour. Dune manire gnrale, il faut empcher les autres processus daccder un objet partag si cet objet est en train dtre utilis par un processus, ce quon appelle dassurer lexclusion mutuelle. Les situations de ce type, o deux ou plusieurs processus lisent ou crivent des donnes partages et o le rsultat dpend de lordonnancement des processus, sont qualies daccs concurrents.

6.2 Objets et sections critiques


Nous allons utiliser les dnitions suivantes : Objet critique : Objet qui ne peut tre accd simultanment. Comme par exemple, les imprimantes, la mmoire, les chiers, les variables etc. Lobjet critique de lexemple du spool dimpression prcdent est la variable partage in. Section critique : Ensemble de suites dinstructions qui oprent sur un ou plusieurs objets critiques et qui peuvent produire des rsultats imprvisibles lorsquelles sont excutes simultanment par des processus diffrents. Ainsi, la section critique de lexemple du spool dimpression est :

6.2. OBJETS ET SECTIONS CRITIQUES

Lecture de la variable in. Linsertion dun nom de fichier dans le rpertoire. La mise jour de in. Imaginez maintenant quon dsire effectuer une somme des premiers nombres en utilisant des processus lgers. Une vision simpliste pour rsoudre ce problme, pourrait suggrer de diviser le travail en deux processus : le premier qui va calculer S1=1+2+...+M avec M=N % 2 et un deuxime qui additionne S2=M+(M+1)+...+N, et nalement la variable somme_totale=S1 + S2 fournira la rponse. La gure 6.2 illustre cette situation. Un code possible pour rsoudre ce problme est :

Processus lger principal

ni 1 nf 50 S1 1 + ... + 50 S2 51 + ... + 100

ni 51 nf 100

somme_total S1 +S2

F IG . 6.2 Somme de

nombres avec threads.

int somme_totale = 0; void somme_partielle(int ni, int nf) { int j = 0; int somme_partielle = 0; for (j = ni; j <= nf; j++) somme_partielle += j; somme_totale += somme_partielle; pthread_exit(0); }

Mais, avec cet algorithme, si plusieurs processus excutent concurremment ce code, on peut obtenir un rsultat incorrect. La solution correcte ncessite lutilisation des sections critiques.

CHAPITRE 6. SYNCHRONISATION DES PROCESSUS

Le problme de conit daccs serait rsolu, si on pouvait assurer que deux processus ne soient jamais en section critique en mme temps au moyen de lexclusion mutuelle). En gnral, les processus qui excutent des sections critiques sont structurs comme suit : Section non critique. Demande dentre en section critique. Section critique. Demande de sortie de la section critique. Section non critique. Quatre conditions sont ncessaires pour raliser correctement une exclusion mutuelle : 1. Deux processus ne peuvent tre en mme temps en section critique. 2. Aucune hypothse ne doit tre faite sur les vitesses relatives des processus et sur le nombre de processeurs. 3. Aucun processus suspendu en dehors dune section critique ne doit bloquer les autres processus. 4. Aucun processus ne doit attendre trop longtemps avant dentrer en section critique. Comment assurer alors une coopration correcte et efcace des processus ?

6.3 Masquage des interruptions


Avant dentrer dans une section critique, le processus masque les interruptions. Il les restaure la n de la section critique. Il ne peut tre alors suspendu durant lexcution de la section critique. Cependant cette solution est dangereuse, car si le processus, pour une raison ou pour une autre, ne restaure pas les interruptions la sortie de la section critique, ce serait la n du systme. La solution nassure pas lexclusion mutuelle, si le systme nest pas monoprocesseur car le masquage des interruptions concernera uniquement le processeur qui a demand linterdiction. Les autres processus excuts par un autre processeur pourront donc accder aux objets partags. En revanche, cette technique est parfois utilise par le systme dexploitation pour mettre jour des variables ou des listes partages par ses processus, par exemple la liste des processus prts.

6.4. EXCLUSION MUTUELLE PAR ATTENTE ACTIVE

6.4 Exclusion mutuelle par attente active


6.4.1 Les variables de verrouillage
Une autre tentative pour assurer lexclusion mutuelle est dutiliser une variable de verrouillage partage verrou, unique, initialise 0. Pour rentrer en section critique (voir algorithme de verrouillage), un processus doit tester la valeur du verrou. Si elle est gale 0, le processus modie la valeur du verrou 1 et excute sa section critique. la n de la section critique, il remet le verrou 0. Sinon, il attend (par une attente active) que le verrou devienne gal 0, cest--dire : while(verrou !=0) ; Algorithme verrouillage Algorithm 1 Verrouillage while verrou 0 do ; // Attente active end while verrou 1 Section_critique() ; verrou 0

Problmes Cette mthode nassure pas lexclusion mutuelle : Supposons quun processus est suspendu juste aprs avoir lu la valeur du verrou qui est gal 0. Ensuite, un autre processus est lu. Ce dernier teste le verrou qui est toujours gal 0, met verrou 1 et entre dans sa section critique. Ce processus est suspendu avant de quitter la section critique. Le premier processus est alors ractiv, il entre dans sa section critique et met le verrou 1. Les deux processus sont en mme temps en section critique. Ainsi cet algorithme nest pas correct. On voit ainsi quil est indispensable dans lnonc du problme de prciser quelles sont les actions atomiques, cest--dire celles qui sont non divisibles. Si on possde sous forme hardware une instruction test and set qui sans tre interrompue lit une variable, vrie si elle est gale 0 et dans ce cas transforme sa valeur en 1, alors il est facile de rsoudre le problme de lexclusion mutuelle en utilisant un verrou. Toutefois on na pas toujours disponible ce type dinstruction, et il faut alors trouver une solution logicielle.

CHAPITRE 6. SYNCHRONISATION DES PROCESSUS

6.4.2 Lalternance
Une autre proposition consiste utiliser une variable tour qui mmorise le tour du processus qui doit entrer en section critique. tour est initialise 0.
// Processus P1 while (1) { // attente active while (tour !=0) ; Section_critique() ; tour = 1 ; Section_noncritique() ; ... } // Processus P2 while (1) { // attente active while (tour !=1) ; Section_critique() ; tour = 0 ; Section_noncritique() ; ... }

Supposons maintenant que le processus P1 lit la valeur de tour qui vaut 0 et entre dans sa section critique. Il est suspendu et P2 est excut. P2 teste la valeur de tour qui est toujours gale 0. Il entre donc dans une boucle en attendant que tour prenne la valeur 1. Il est suspendu et P1 est lu de nouveau. P1 quitte sa section critique, met tour 1 et entame sa section non critique. Il est suspendu et P2 est excut. P2 excute rapidement sa section critique, tour = 0 et sa section non critique. Il teste tour qui vaut 0. Il attend que tour prenne la valeur 1. Problme : On peut vrier assez facilement que deux processus ne peuvent entrer en section critique en mme temps, toutefois le problme nest pas vraiment rsolu car il est possible quun des deux processus ait plus souvent besoin dentrer en section critique que lautre ; lalgorithme lui fera attendre son tour bien que la section critique ne soit pas utilise. Un processus peut tre bloqu par un processus qui nest pas en section critique.

6.4. EXCLUSION MUTUELLE PAR ATTENTE ACTIVE

6.4.3 Solution de Peterson


La solution de Peterson se base sur lutilisation de deux fonctions : entrer_region() ; quitter_region() ; Chaque processus doit, avant dentrer dans sa section critique appeler la fonction entrer_region() en lui fournissant en paramtre son numro de processus. Cet appel le fera attendre si ncessaire jusqu ce quil ny ait plus de risque. A la n de la section critique, le processus doit appeler quitter_region() pour indiquer quil quitte sa section critique et pour autoriser laccs aux autres processus, comme le montre le code peterson.c.

Listing 6.1 peterson.c


# define FALSE 0 # define TRUE 1 # define N 2 int tour ; i n t i n t e r e s s e [N ] ;

// Nb . p r o c e s s u s // Le t o u r du p r o c e s s u s // 0 i n i t i a l e

10

void e n t r e r _ r e g i o n ( i n t p r o c e s s u s ) // p r o c e s s u s 0 ou 1 { int autre ; // Autre p r o c e s s u s autre = 1 processus ; i n t e r e s s e [ p r o c e s s u s ] = TRUE ; // On e s t i n t e r e s s t o u r = p r o c e s s u s ; // P o s i t i o n e r l e drapeau // Attendre while ( t o u r = = p r o c e s s u s && i n t e r e s s e [ a u t r e ] = =TRUE ) ; } void q u i t t e r _ r e g i o n ( i n t p r o c e s s u s ) { // P r o c e s s u s q u i t e r e g i o n c r i t i q u e i n t e r e s s e [ p r o c e s s u s ] = FALSE ; }

20

Prouver la correction de cet algorithme est quivalent montrer le fait que deux processus ne peuvent pas entrer en section critique en mme temps. Pour y arriver, nous devons rcrire le code, comme montr dans peterson2.c :

Listing 6.2 peterson2.c


# define FALSE 0 # define TRUE 1

8
# define N 2

CHAPITRE 6. SYNCHRONISATION DES PROCESSUS


// Nb . p r o c e s s u s // Le t o u r du p r o c e s s u s // 0 i n i t i a l e

int tour ; i n t i n t e r e s s e [N ] ;

10

void E n t r e r S o r t i r R e g i o n ( p r o c e s s u s ) { autre = 1 processus ; i n t e r e s s e [ p r o c e s s u s ] = TRUE ; tour = processus ; while ( ( t o u r = = i ) & & i n t e r e s s e [ a u t r e ] ) ; Section_critique ( ) ; i n t e r e s s e [ p r o c e s s u s ] = FALSE ; }

Pour vrier que lalgorithme fonctionne correctement et donne accs aux deux processus la rgion critique, il faut dcomposer cet algorithme en actions atomiques. Voici celles pour le processus P0 :
1. 2. 3. 4. 5. 6. 7. 8. 9. crire (true, interesse[0]); crire (0, tour); a0 = Lire (interesse[1]); Si (!a0) aller 7; t0 = Lire(tour); Si (t0 == 0) aller 3; Section_critique(); crire (false, interesse[0]); Aller 1;

Les actions atomiques de P1 sont tout fait semblables :


1. 2. 3. 4. 5. 6. 7. 8. 9. crire (true, interesse[1]); crire (1, tour); a1 = Lire (interesse[0]); Si (!a1) aller 7; t1 = Lire(tour); Si (t1 == 1) aller 3; Section_critique(); crire (false, interesse[1]); Aller 1;

Il faut examiner tous les enchevtrements possibles des deux suites dactions atomiques. On peut pour des raisons de symtrie supposer que le processus P0 excute sa premire instruction avant P1. Il faut toutefois examiner plusieurs cas par la suite :

6.4. EXCLUSION MUTUELLE PAR ATTENTE ACTIVE

Cas 1 : P0 excute 3 avant que P1 nexcute 1 les valeurs obtenues par les deux processus sont alors : a0 = false, t0 = 0, a1 = true, t1 = 1 ; Dans ce cas on vrie que P0 entre en section critique, P1 reste bloqu. Cas 2 : P1 excute 1 avant que P0 nexcute 3, et 2 aprs que P0 ait excut 5 a0 = true, t0 = 0, a1 = true , t1 = 1 ; On constate quaucun des deux processus ne rentre dans la section critique, toutefois immdiatement aprs le processus P0 obtient : a0 = true, t0 = 1, ainsi P0 entre en section critique, P1 bloqu Cas 3 : P1 excute 1 avant que P0 nexcute 3, et 2 entre 2 et 5. On a dans ce cas : a0 = true, t0 = 1, a1 = true , t1 = 1 ; ainsi P0 entre en section critique, P1 reste bloqu Cas 4 : P1 excute 1 avant que P0 nexcute 2, et 2 avant 2, donc a0 = true, t0 = 0, a1 = true , t1 = 0 ; P1 entre en section critique et P0 reste bloqu Ayant dmontr la correction de lalgorithme, il faut aussi se poser la question de savoir si deux processus ne peuvent rester bloqus indniment en attendant la section critique, on dirait alors quil y a interblocage. Des raisonnements analogues ceux faits pour la correction montrent quil nen est rien. Cependant, lalgorithme marche seulement pour deux processus.

6.4.4 Linstruction TSL


Les ordinateurs multiprocesseurs ont une instruction atomique indivisible appele TSL (Test and Set Lock ).
tsl registre, verrou

Linstruction TSL excute en un seul cycle, de manire indivisible, le chargement dun mot mmoire dans un registre et le rangement dune valeur non nulle ladresse du mot charg. Lorsquun processeur excute linstruction TSL, il verrouille le bus de donnes pour empcher les autres processeurs daccder la mmoire pendant la dure de lopration. Cette instruction peut tre utilise pour tablir et supprimer des verrous (assurer lexclusion mutuelle).

10

CHAPITRE 6. SYNCHRONISATION DES PROCESSUS

entrer_region Label : tsl registre, verrou # # cmp registre, $0 # jnz Label # ret # quitter_region mov verrou, $0 ret

Copier verrou dans reg. verrou = 1 verrou = 0 ? si different on boucle (verrou) ret. entrer section critique

# mettre 0 dans verrou # quitter section critique

6.4.5 Commentaires sur lattente active


Lattente active se traduit dans une consommation du temps UCT, et un processus en attente active peut rester en permanence dans une boucle innie. Par exemple, supposons que : Deux processus H et B, tels que H est plus prioritaire que B partagent un objet commun. Les rgles dordonnancement font que H sera excut ds quil se trouve dans ltat prt. Pendant que H tait en E/S, le processus B est entr dans sa section critique. Le processus H est redevenu prt avant que B ait quitt sa section critique. Le processus B est ensuite suspendu et le processus H est lu. H effectue alors une attente active et B ne peut tre slectionn puisque H est plus prioritaire. Conclusion ? le processus B ne peut plus sortir de sa section critique et H boucle indniment. Une dernire question est de vrier quil y a un minimum dquit entre les deux processus, cest--dire que lun dentre eux ne peut pas attendre indniment son tour daccs la section critique alors que lautre y accde un nombre inni de fois. Cette question est plus difcile, de mme que la gnralisation de la solution un nombre quelconque de processus.

6.5 Primitives SLEEP et WAKEUP


SLEEP() est un appel systme qui suspend lappelant en attendant quun autre le rveille. WAKEUP(processus) est un appel systme qui rveille un processus. Par exemple, un processus H qui veut entrer dans

6.5. PRIMITIVES SLEEP ET WAKEUP

11

sa section critique est suspendu si un autre processus B est dj dans sa section critique. Le processus H sera rveill par le processus B, lorsquil quitte la section critique. Problme : Si les tests et les mises jour des conditions dentre en section critique ne sont pas excuts en exclusion mutuelle , des problmes peuvent survenir. Si le signal mis par WAKEUP() arrive avant que le destinataire ne soit endormi, le processus peut dormir pour toujours. Exemples
// Processus P1 { if(cond ==0 ) SLEEP() ; cond = 0 ; section_critique ( ) ; cond = 1 ; WAKEUP(P2) ; ... } // Processus P2 { if (cond ==0 ) SLEEP() ; cond = 0 ; section_critique ( ) ; cond = 1 ; WAKEUP(P1) ; ... }

Cas 1 : Supposons que : P1 est excut et il est suspendu juste aprs avoir lu la valeur de cond qui est gale 1. P2 est lu et excut. Il entre dans sa section critique et est suspendu avant de la quitter. P1 est ensuite excut. Il entre dans sa section critique. Les deux processus sont dans leurs sections critiques. Cas 2 : Supposons que : P1 est excut en premier. Il entre dans sa section critique. Il est suspendu avant de la quitter ; P2 est lu. Il lit cond qui est gale 0 et est suspendu avant de la tester.

12

CHAPITRE 6. SYNCHRONISATION DES PROCESSUS


P1 est lu de nouveau. Il quitte la section critique, modie la valeur de cond et envoie un signal au processus P2. Lorsque P2 est lu de nouveau, comme il nest pas endormi, il ignorera le signal et va sendormir pour toujours.

Dans le systme Unix, les appels systme qui assurent peu prs, les mmes fonctions que les primitives SLEEP et WAKEUP sont : pause( ) : suspend le processus appelant jusqu rception dun signal. kill(pid, SIGCONT) : envoie le signal SIGCONT au processus pid. Lmission dun signal est possible si lune des deux conditions est vrie : 1. Lmetteur et le destinataire du signal appartiennent au mme propritaire 2. Lmetteur du signal est le super-utilisateur.

6.6 Problme du producteur et du consommateur


Dans le problme du producteur et du consommateur deux processus partagent une mmoire tampon de taille xe, comme montr la gure 6.3. Lun dentre eux, le producteur, met des informations dans la mmoire tampon, et lautre, les retire. Le producteur peut produire uniquement si le tampon nest pas plein. Le producteur doit tre bloqu tant et aussi longtemps que le tampon est plein. Le consommateur peut retirer un objet du tampon uniquement si le tampon nest pas vide. Le consommateur doit tre bloqu tant et aussi longtemps que le tampon est vide. Les deux processus ne doivent pas accder en mme temps au tampon. Exemple 1. Une solution classique au problme du producteur et du consommateur [?] est montr sur le programme schema-prod-cons.c avec lutilisation des primitives SLEEP et WAKEUP.

Listing 6.3 schema-prod-cons.c


# d e f i n e TRUE 1 # define N 5 0 i n t compteur = 0 ;

// Nb . de p l a c e s dans l e tampon // Nb . o b j e t s dans l e tampon

void producteur ( ) { while (TRUE) { produire_objet ( ) ;

6.6. PROBLME DU PRODUCTEUR ET DU CONSOMMATEUR

13

Processus producteur

Processus consommateur

Flux de donnes

Mcanisme de communication

F IG . 6.3 Problme du producteur/consommateur.


10 i f ( compteur = = N) SLEEP ( ) ; mettre_objet ( ) ; compteur ++; i f ( compteur = = 1 ) WAKEUP( consomateur ) ;

// Tampon p l e i n // dans tampon // Tampon vide

} 20

30

void consomateur ( ) { while (TRUE) { i f ( ! compteur ) SLEEP ( ) ; retirer_objet ( ) ; compteur  ; i f ( compteur = = N 1 ) WAKEUP( producteur ) ; consommer_objet ( ) ; } }

// tampon vide // du tampon // r v e i l l e r producteur

6.6.1 Critique des solutions prcdentes


La gnralisation de toutes ces solutions aux cas de plusieurs processus est bien complexe. Le mlange dexclusion mutuelle et de suspension est toujours dlicat raliser et implanter.

14

CHAPITRE 6. SYNCHRONISATION DES PROCESSUS

6.7 Smaphores
Pour contrler les accs un objet partag, E. W. Dijkstra suggra en 1965 lemploi dun nouveau type de variables appeles les smaphores. Un smaphore est un compteur entier qui dsigne le nombre dautorisations daccs une section critique. Il a donc un nom et une valeur initiale, par exemple semaphore S = 10. Les smaphores sont manipuls au moyen des oprations P ou wait et V ou signal. Lopration P(S) dcrmente la valeur du smaphore S si cette dernire est suprieure 0. Sinon le processus appelant est mis en attente. Le test du smaphore, le changement de sa valeur et la mise en attente ventuelle sont effectus en une seule opration atomique indivisible. Lopration V(S) incrmente la valeur du smaphore S, si aucun processus nest bloqu par lopration P(S). Sinon, lun dentre eux sera choisi et redeviendra prt. V est aussi une opration indivisible. Les smaphores permettent de raliser des exclusions mutuelles, comme illustr la gure 6.4. semaphore mutex=1 ; processus P1 : P(mutex) section critique de P1 ; V(mutex) ; processus P2 ; P(mutex) section critique de P2 ; V(mutex) ;

F IG . 6.4 Exclusion mutuelle avec des smaphores. La gure 6.5 montre lexclusion mutuelle dune petite section critique a++. int a ; semaphore mutex=1 ; processus P1 : processus P2 ; P(mutex) P(mutex) a++ ; a++ ; V(mutex) ; V(mutex) ; F IG . 6.5 Exclusion mutuelle dune section critique compose dune variable a. Sur la gure 6.6 on montre la valeur que les smaphores prennent lors des appels des P (wait) et V (signal), pour trois processus  ,  et

6.8. SERVICES POSIX SUR LES SMAPHORES

15



.
Valeur du smaphore (s)
1 0 -1 -2 wait(s) wait(s) wait(s)

P0

P1

P2

dbloquer

-1

signal(s)
dbloquer

signal(s)

Excution dun code de la section critique Processus bloqu par le smaphore

signal(s)

F IG . 6.6 Valeurs de smaphores lors des appels V (signal) et P (wait).

6.8 Services Posix sur les smaphores


Le systme dexploitation Linux permet de crer et dutiliser les smaphores dnis par le standard Posix. Les services Posix de manipulation des smaphores se trouvent dans la librairie <semaphore.h>. Le type smaphore est dsign par le type sem_t. int sem_init(sem_t *sem, int pshared, unsigned int valeur) Initialisation dun smaphore. sem est un pointeur sur le smaphore initialiser ; value est la valeur initiale du smaphore ; pshared indique si le smaphore est local au processus pshared=0 ou partag entre le pre et le ls pshared 0. Actuellement Linux ne supporte pas les smaphores partags. int sem_wait(sem_t *sem) est quivalente lopration P : retourne toujours 0. int sem_post(sem_t *sem) est quivalente lopration V : retourne 0 en cas de succs ou -1 autrement.

16

CHAPITRE 6. SYNCHRONISATION DES PROCESSUS

int sem_trywait(sem_t *sem) dcrmente la valeur du smaphore sem si sa valeur est suprieure 0 ; sinon elle retourne une erreur. Cest une opration atomique. int sem_getvalue (sem_t* nom, int * sval) ; rcuprer la valeur dun smaphore : il retourne toujours 0. int sem_destroy (sem_t* nom) ; dtruire un smaphore. Retourne -1 sil y a encore des waits. Les smaphores Posix servent donc synchroniser les communications entre threads. Les smaphores dUnix System V sont un autre type de mcanisme qui permet la synchronisation entre processus. Sa gestion tant un peu plus complexe, on verra ce mcanisme dans le Chapitre ??, Communications IPC System V. Exemple 2. Le programme sem-posix.c sem-posix.c montre lutilisation de smaphores Posix.

Listing 6.4 sem-posix.c


# include # include # include # include # include # include < s t d i o . h> < s t d l i b . h> < s t r i n g . h> < pthread . h> < u n i s t d . h> < semaphore . h>

10

sem_t my_sem ; # d e f i n e SEMINITVALUE 1 void  c r i t i c a l _ s e c t i o n ( void # d e f i n e MAXTHREADS 1 0 0

);

20

i n t main ( i n t a r g c , char  argv [ ] ) { int n , i , error ; p t h r e a d _ t t h r e a d i d [MAXTHREADS] ; i f ( ( a r g c ! = 2 ) | | ( ( n = a t o i ( argv [ 1 ] ) ) < = 0 ) ) { p r i n t f ( " Usage : % s nombre de t h r e a d s \n " , argv [ 0 ] ) ; exit (1 ); } // i n i t i a l i s e r l e smaphore s e m _ i n i t(&my_sem , 0 , SEMINITVALUE ) ; // c r a t i o n des t h r e a d s for ( i = 0 ; i < n ; ++ i ) p t h r e a d _ c r e a t e (& t h r e a d i d [ i ] , NULL, c r i t i c a l _ s e c t i o n , ( void // a t t e n d r e l a f i n des t h r e a d s for ( i = 0 ; i < n ; ++ i )

) i );

6.8. SERVICES POSIX SUR LES SMAPHORES


p t h r e a d _ j o i n ( t h r e a d i d [ i ] , NULL) ; exit (0);

17

30

40

void  c r i t i c a l _ s e c t i o n ( void  arg ) { i n t myindex , i , n ; // e x t r a i r e l index des t h r e a d s myindex = ( i n t ) arg ; sem_wait(&my_sem ) ; // s e c t i o n c r i t i q u e f o r ( i = 1 ; i < 5 ; i ++) { p r i n t f ( "%d : % d\n " , myindex , i ) ; i f ( ( n= s l e e p ( 1 ) ) ! = 0 ) // dormir 1 seconde p r i n t f ( " i n t e r r u p t e d , no o f s e c s l e f t %d\n " , n ) ; } // q u i t e r s e c t i o n c r i t i q u e sem_post (&my_sem ) ; p t h r e a d _ e x i t (NULL ) ; }

Sortie du programme posix-sem.c :


leibnitz> gcc -o posix-sem posix-sem.c -lpthread leibnitz> posix-sem 2 0 : 1 0 : 2 0 : 3 0 : 4 1 : 1 1 : 2 1 : 3 1 : 4 leibnitz>

Exemple 3. Producteur/consommateur avec smaphores. La solution du problme au moyen des smaphores Posix utilise trois smaphores dans le programme prod-cons.cpp. Le premier smaphore, nomm plein, compte le nombre demplacements occups. Il est initialis 0. Le second, nomm vide, compte le nombre demplacements libres. Il est initialis N (la taille du tampon). Le dernier, nomm mutex, assure lexclusion mutuelle pour laccs au tampon (lorsquun processus utilise le tampon, les autres ne peuvent pas y accder).

18

CHAPITRE 6. SYNCHRONISATION DES PROCESSUS


Listing 6.5 prod-cons.cpp

# include # include # include # include

< semaphore . h> < u n i s t d . h > // s l e e p < pthread . h> < s t d i o . h>

10

# define t a i l l e 3 sem_t p l e i n , vide , mutex ; i n t tampon [ t a i l l e ] ; p t h r e a d _ t cons , prod ; void  consommateur ( void  ) ; void  producteur ( void  ) ; i n t main ( ) { // i n i t i l i s e r l e s smaphores s e m _ i n i t(& p l e i n , 0 , 0 ) ; s e m _ i n i t(& vide , 0 , t a i l l e ) ; s e m _ i n i t(&mutex , 0 , 1 ) ; // c r e r l e s t h r e a d s p t h r e a d _ c r e a t e (& cons ,NULL, consommateur ,NULL) ; p t h r e a d _ c r e a t e (&prod ,NULL, producer ,NULL) ; // 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 ( prod ,NULL) ; p t h r e a d _ j o i n ( cons ,NULL) ; p r i n t f ( " f i n des t h r e a d \ n " ) ; return 0 ; } void  consommateur ( void  ) { i n t i c = 0 , nbcons = 0 , o b j e t ; do { sem_wait(& p l e i n ) ; sem_wait(&mutex ) ; // consommer o b j e t = tampon [ i c ] ; sem_post (&mutex ) ; sem_post (& vide ) ; p r i n t f ( " i c i cons . : tampon[%d]= %d\n " , i c , o b j e t ) ; i c =( i c +1)% t a i l l e ; nbcons ++; sleep ( 2 ) ; } while ( nbcons < = 5 ) ; r e t u r n ( NULL) ; } void  producteur ( void

20

30

40

6.8. SERVICES POSIX SUR LES SMAPHORES


{ 50 i n t i p = 0 , nbprod = 0 , o b j e t = 0 ; do { sem_wait(& vide ) ; sem_wait(&mutex ) ; // produire tampon [ i p ]= o b j e t ; sem_post (&mutex ) ; sem_post (& p l e i n ) ; p r i n t f ( " i c i prod . : tampon[%d]= %d\n " , i p , o b j e t ) ; o b j e t ++; nbprod ++; i p =( i p +1)% t a i l l e ; } while ( nbprod < = 5 ) ; r e t u r n NULL;

19

60

Sortie du programme prod-cons.cpp :


leibnitz> g++ -o prod-cons prod-cons.cpp -lpthread leibnitz> prod-cons ici cons. :tampon[0]= 0 ici prod. : tampon[0]= 0 ici prod. : tampon[1]= 1 ici prod. : tampon[2]= 2 ici prod. : tampon[0]= 3 ici cons. :tampon[1]= 1 ici prod. : tampon[1]= 4 ici cons. :tampon[2]= 2 ici prod. : tampon[2]= 5 ici cons. :tampon[0]= 3 ici cons. :tampon[1]= 4 ici cons. :tampon[2]= 5 fin des thread leibnitz>

6.8.1 Problme des philosophes


Il sagit dun problme thorique trs interessant proposs et rsolu aussi par Dijkstra. Cinq philosophes sont assis autour dune table. Sur la table, il y a alternativement cinq plats de spaghettis et cinq fourchettes (gure 6.7). Un philosophe passe son temps manger et penser. Pour manger son plat de spaghettis, un philosophe a besoin de deux fourchettes qui sont de part et dautre de son plat. crire un programme qui permette

20

CHAPITRE 6. SYNCHRONISATION DES PROCESSUS

chaque philosophe de se livrer ses activits (penser et manger) sans jamais tre bloqu.

F IG . 6.7 Problme des philosophes. Il est important de signaler que si tous les philosophes prennent en mme temps chacun une fourchette, aucun dentre eux ne pourra prendre lautre fourchette (situation dinterblocage). Pour viter cette situation, un philosophe ne prend jamais une seule fourchette. Les fourchettes sont les objets partags. Laccs et lutilisation dune fourchette doit se faire en exclusion mutuelle. On utilisera le smaphore mutex pour raliser lexclusion mutuelle.

Exemple 4. Une solution au problme des philosophes. Listing 6.6 philosophe.c

# include # include # include # include

< s t d i o . h> < unistd . h> // pour s l e e p < pthread . h > // pour l e s t h r e a d s < semaphore . h > // pour l e s smaphores

10

# define N 5 // nombre de p h i l o s o p h e s # define G ( ( N+ i 1)%N) // philosophe de gauche de i # define D (i) // philosophe de d r o i t e de i # define l i b r e 1 # define occupe 0 i n t fourch [N] = { l i b r e , l i b r e , l i b r e , l i b r e , l i b r e } ; sem_t mutex ; void  philosophe ( void  ) ;

6.8. SERVICES POSIX SUR LES SMAPHORES


i n t main ( ) { i n t NumPhi [N ] = { 0 , 1 , 2 , 3 , 4 } ; int i ; p t h r e a d _ t ph [N] ; s e m _ i n i t(&mutex , 0 , 1 ) ; // c r a t i o n des N p h i l o s o p h e s f o r ( i = 0 ; i <N ; i ++) p t h r e a d _ c r e a t e (&ph [ i ] , NULL, philosophe , & (NumPhi [ i ] ) ) ; // a t t e n d r e l a f i n des t h r e a d s i =0; while ( i <N & & ( p t h r e a d _ j o i n ( ph [ i + + ] ,NULL) = = 0 ) ) ; p r i n t f ( " f i n des t h r e a d s \ n " ) ; return 0 ; } // La f o n c t i o n de chaque philosophe void  philosophe ( void  num) { i n t i =  ( i n t  ) num ; i n t nb = 2 ; while ( nb ) { // penser sleep ( 1 ) ; // e s s a y e r de prendre l e s f o u r c h e t t e s pour manger sem_wait(&mutex ) ; i f ( fourch [G] & & fourch [ i ] ) { fourch [G ] = 0 ; fourch [ i ] = 0 ; p r i n t f ( " philosophe [%d ] mange \ n " , i ) ; sem_post (&mutex ) ; nb  ; // manger sleep ( 1 ) ; // l i b r e r l e s f o u r c h e t t e s sem_wait(&mutex ) ; fourch [G ] = 1 ; fourch [ i ] = 1 ; p r i n t f ( " philosophe [%d ] a f i n i de manger\n " , i ) ; sem_post (&mutex ) ; } e l s e sem_post (&mutex ) ; } }

21

20

30

40

50

60

Problme : Cette solution rsout le problme dinterblocage. Mais, un phi-

22

CHAPITRE 6. SYNCHRONISATION DES PROCESSUS


losophe peut mourir de faim car il ne pourra jamais obtenir les fourchettes ncessaires pour manger (problme de famine et dquit).

Pour viter le problme de famine il faut garantir que si un processus demande dentrer en section critique, il obtient satisfaction au bout dun temps ni. Dans le cas des philosophes, le problme de famine peut tre vit, en ajoutant N smaphores (un smaphore pour chaque philosophe). Les smaphores sont initialiss 0. Lorsquun philosophe i ne parvient pas prendre les fourchettes, il se met en attente ( P(S[i]) ). Lorsquun philosophe termine de manger, il vrie si ses voisins sont en attente. Si cest le cas, il rveille les voisins qui peuvent manger en appelant lopration V. On distingue trois tats pour les philosophes : penser, manger et faim.

Exemple 5. Une meilleure solution au problme des philosophes. Listing 6.7 philosophe2.c

# include # include # include # include

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

10

# define N 5 # define G ( ( N+ i 1)%N) // philosophe de gauche de i # define D ( i ) // philosophe de d r o i t e de i enum e t a t { penser , faim , manger } ; e t a t E t a t [N] = { penser , penser , penser , penser , penser } ; sem_t S [N ] ; sem_t mutex ; void  philosophe ( void  ) ; void T e s t ( i n t i ) ; i n t main ( ) { i n t i , NumPhi [N ] = { 0 , 1 , 2 , 3 , 4 } ; p t h r e a d _ t ph [N] ; s e m _ i n i t (&mutex , 0 , 1 ) ; f o r ( i = 0 ; i <N ; i ++) s e m _ i n i t(&S [ i ] , 0 , 0 ) ; // c r a t i o n des N p h i l o s o p h e s f o r ( i = 0 ; i <N ; i ++) p t h r e a d _ c r e a t e (&ph [ i ] , NULL, philosophe , & (NumPhi [ i ] ) ) ; // a t t e n d r e l a f i n des t h r e a d s f o r ( i = 0 ; ( i <N && p t h r e a d _ j o i n ( ph [ i ] , NULL) = = 0 ) ; i + + ) ; p r i n t f ( " f i n des t h r e a d s \ n " ) ; return 0 ; }

20

30

6.8. SERVICES POSIX SUR LES SMAPHORES


void  philosophe ( void  num) { i n t i =  ( i n t  ) num ; int nb = 2 ; while ( nb ) { nb  ; // penser sleep ( 1 ) ; // t e n t e r de manger sem_wait(&mutex ) ; E t a t [ i ]= faim ; Test ( i ) ; sem_post (&mutex ) ; sem_wait(&S [ i ] ) ; // manger p r i n t f ( " philosophe [%d ] mange\n " , i ) ; s l e e p ( 1 ) ; // manger p r i n t f ( " philosophe [%d ] a f i n i de manger\n " , i ) ; // l i b r e r l e s f o u r c h e t t e s sem_wait(&mutex ) ; E t a t [ i ] = penser ; // v r i f i e r s i s e s v o i s i n s peuvent manger T e s t (G ) ; T e s t (D ) ; sem_post (&mutex ) ; } } // procedure qui v r i f i e s i l e philosophe i peut manger void T e s t ( i n t i ) { i f ( ( E t a t [ i ] = = faim ) & & ( E t a t [G ] ! = manger ) && ( E t a t [D] ! = manger ) ) { E t a t [ i ] = manger ; sem_post (&S [ i ] ) ; } }

23

40

50

60

Excution de philosophe2.c
leibnitz> g++ -o philosophe2 philosophe2.c -lpthread leibnitz> philosophe2 philosophe[0] mange philosophe[2] mange philosophe[4] mange philosophe[0] a fini de manger

24

CHAPITRE 6. SYNCHRONISATION DES PROCESSUS


de manger de manger

philosophe[2] a fini philosophe[1] mange philosophe[4] a fini philosophe[3] mange philosophe[0] mange philosophe[1] a fini philosophe[2] mange philosophe[3] a fini philosophe[4] mange philosophe[0] a fini philosophe[2] a fini philosophe[1] mange philosophe[3] mange philosophe[4] a fini philosophe[1] a fini philosophe[3] a fini fin des threads leibnitz>

de manger de manger de manger de manger

de manger de manger de manger

6.8.2 Problme des rdacteurs et des lecteurs


Ce problme modlise les accs une base de donnes. Un ensemble de processus tente constamment daccder la base de donnes soit pour crire, soit pour lire des informations. Pour assurer une certaine cohrence des donnes de la base, il faut interdire laccs (en lecture et en criture) tous les processus, si un processus est en train de modier la base (accde la base en mode criture). Par contre, plusieurs processus peuvent accder la base, en mme temps, en mode lecture. Les rdacteurs reprsentent les processus qui demandent des accs en criture la base de donnes. Les lecteurs reprsentent les processus qui demandent des accs en lecture la base de donnes (gure 6.8). Pour contrler les accs la base, on a besoin de connatre le nombre de lecteurs (NbL) qui sont en train de lire. Le compteur NbL est un objet partag par tous les lecteurs. Laccs ce compteur doit tre exclusif (smaphore mutex). Un lecteur peut accder la base, sil y a dj un lecteur qui accde la base (NbL>0) ou aucun rdacteur nest en train dutiliser la base. Un rdacteur peut accder la base, si elle nest pas utilise par les autres (un accs exclusif la base). Pour assurer cet accs exclusif, on utilise un autre smaphore : Redact. Des algorithmes adquats pour le lecteur() et le redacteur() sont les suivants : NbL 0 // Nb Lecteurs

6.8. SERVICES POSIX SUR LES SMAPHORES

25

Lecteur

Lecteur

crivain

Lecteur

crivain

Ressource

F IG . 6.8 Problme des lecteurs et crivains. semaphore Redact // crivain semaphore Mutex // accs la base lecteur : repeat P(Mutex) if (NbL == 0) then P(Redact) // Premier lecteur empche criture end if NbL NbL + 1 V(Mutex) // lecture de la base // n de laccs la base P(Mutex) NbL NbL - 1 if (NbL == 0) then V(Redact) // Dernier lecteur habilite lcriture end if V(Mutex) until TRUE redacteur : repeat P(Redact) // modier les donnes de la base V(Redact) until TRUE

26

CHAPITRE 6. SYNCHRONISATION DES PROCESSUS

Exemple 6. Le programme red-lec.c fait limplantation de lalgorithme des rdacteurs et lecteurs. Il xe le nombre de lecteurs et rdacteurs N=3, ainsi que le nombre daccs (an dviter une boucle innie) ACCES=4. On a simul des oprations de lecture de la base, des modications des donnes et dcriture dans la base avec des sleep() dune seconde.

Listing 6.8 red-lec.c


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

10

# define N 3 # d e f i n e ACCES 4 i n t NbL = 0 ; sem_t Redact ; sem_t mutex ; void  l e c t e u r ( void  ) ; void  r e d a c t e u r ( void  ) ; i n t main ( ) { int i ; i n t numred [N ] = { 0 , 1 , 2 } ; i n t numlec [N ] = { 0 , 1 , 2 } ; p t h r e a d _ t red [N] ; p t h r e a d _ t l e c [N] ; s e m _ i n i t(&mutex , 0 , 1 ) ; s e m _ i n i t(& Redact , 0 , 1 ) ; f o r ( i = 0 ; i <N ; i ++) { // c r a t i o n des r e d a c t e u r s p t h r e a d _ c r e a t e (& red [ i ] , NULL, l e c t e u r , & ( numred[ i ] ) ) ; // c r a t i o n des l e c t e u r s p t h r e a d _ c r e a t e (& l e c [ i ] , NULL, r e d a c t e u r , & ( numlec [ i ] ) ) ; } // a t t e n d r e l a f i n des t h r e a d s f o r ( i = 0 ; i <N ; i ++) { p t h r e a d _ j o i n ( red [ i ] , NULL) ; p t h r e a d _ j o i n ( l e c [ i ] , NULL) ; } p r i n t f ( " f i n des t h r e a d s \n " ) ; return 0 ; }

20

30

6.8. SERVICES POSIX SUR LES SMAPHORES


40 void  l e c t e u r ( void  num) { i n t i =  ( i n t  ) num ; i n t x=ACCES ; do { p r i n t f ( " L e c t e u r %d\n " , i ) ; sem_wait(&mutex ) ; i f ( ! NbL) sem_wait(& Redact ) ; NbL++; sem_post(&mutex ) ; // l e c t u r e de l a base sleep ( 1 ) ; // f i n de l a c c s l a base sem_wait(&mutex ) ; NbL ; i f ( ! NbL) sem_post (& Redact ) ; sem_post(&mutex ) ; // t r a i t e m e n t des donnes l u e s sleep ( 1 ) ; } while(  x ) ; } void  r e d a c t e u r ( void  num) { i n t i =  ( i n t  ) num ; i n t x=ACCES ; do { p r i n t f ( " Redacteur %d\n " , i ) ; sem_wait(& Redact ) ; // m o d i f i e r l e s donnes de l a base sleep ( 1 ) ; sem_post(& Redact ) ; } while(  x ) ; }

27

50

60

70

Nous montrons ensuite une excution de red-lec.c :


leibnitz> Lecteur 0 Redacteur Lecteur 1 Redacteur Lecteur 2 Redacteur gcc -o red-lec red-lec.c -lpthread ; red-lec 0 1 2

28

CHAPITRE 6. SYNCHRONISATION DES PROCESSUS

Redacteur 0 Lecteur 0 Lecteur 1 Lecteur 2 Redacteur 1 Redacteur 2 Redacteur 0 Lecteur 0 Lecteur 1 Redacteur 1 Lecteur 2 Redacteur 2 Redacteur 0 Lecteur 0 Lecteur 1 Redacteur 1 Lecteur 2 Redacteur 2 fin des threads leibnitz>

6.9 Compteurs dvnements


Un compteur dvnements est un compteur associ un vnement. Sa valeur indique le nombre doccurrences de lvnement associ. Il peut tre utilis pour synchroniser des processus. Par exemple, pour raliser un traitement un processus attend jusqu ce quun compteur dvnement atteigne une valeur. Trois oprations sont dnies pour un compteur dvnement E : Read(E) : donne la valeur de E Advance(E) : incrmente E de 1 de manire atomique Await(E,v) : attend que E atteigne ou dpasse la valeur v

6.9.1 Producteur/consommateur
Pour le problme du producteur et du consommateur, nous pouvons utiliser deux compteurs dvnements in et out. Le premier compte le nombre dinsertions dans le tampon alors que le second compte le nombre de retraits du tampon depuis le dbut.

6.9. COMPTEURS DVNEMENTS

29

in : est incrmente de 1 par le producteur aprs chaque insertion dans le tampon. out : est incrmente de 1 par le consommateur aprs chaque retrait du tampon. Le producteur doit sarrter de produire lorsque le tampon est plein. La condition satisfaire pour pouvoir produire est : N > in - out. Posons sequence = in + 1. La condition devient alors : out sequence - N De mme, le consommateur peut consommer uniquement si le tampon nest pas vide. La condition satisfaire pour pouvoir consommer est : in - out > 0. Posons sequence = out + 1. La condition devient alors : in sequence.
// producteur consommateur #define N 100 // taille du tampon int in = 0, out = 0 ; void producteur (void) { int objet , sequence =0, pos =0 ; while (1) { objet = objet+1 ; sequence = sequence + 1 ; // attendre que le tampon devienne non plein Await(out, sequence-N) ; tampon[pos]= objet ; pos = (pos+1)%N ; // incrmenter le nombre de dpts Advance(&int) ; } } void consommateur (void) { int objet , sequence =0, pos =0 ; while (1) { sequence = sequence + 1 ; // attendre que le tampon devienne non vide Await(in,sequence) ; objet = tampon[pos] ;

30

CHAPITRE 6. SYNCHRONISATION DES PROCESSUS


// incrmenter le compteur de retraits Advance(&out) ; printf(" objet consomm [%d]:%d ", pos, objet) ; pos = (pos+1)%N ; }

6.10 Moniteurs
Lide des moniteurs est de regrouper dans un module spcial, appel moniteur, toutes les sections critiques dun mme problme. Un moniteur est un ensemble de procdures, de variables et de structures de donnes. Les processus peuvent appeler les procdures du moniteur mais ils ne peuvent pas accder la structure interne du moniteur partir des procdures externes. Pour assurer lexclusion mutuelle, il suft quil y ait tout moment au plus un processus actif dans le moniteur. Cest le compilateur qui se charge de cette tche. Pour ce faire, il rajoute, au dbut de chaque procdure du moniteur un code qui ralise ce qui suit : sil y a processus actif dans le moniteur, alors le processus appelant est suspendu jusqu ce que le processus actif dans le moniteur se bloque en excutant un wait sur une variable conditionnelle (wait(c)) ou quitte le moniteur. Le processus bloqu est rveill par un autre processus en lui envoyant un signal sur la variable conditionnelle ( signal(c) ). Cette solution est plus simple que les smaphores et les compteurs dvnements, puisque le programmeur na pas se proccuper de contrler les accs aux sections critiques. Mais, malheureusement, lexception de JAVA, la majorit des compilateurs utiliss actuellement ne supportent pas les moniteurs.

6.10.1 Producteur/consommateur
Les sections critiques du problme du producteur et du consommateur sont les oprations de dpt et de retrait dans le tampon partag. Le dpt est possible si le tampon nest pas plein. Le retrait est possible si le tampon nest pas vide.
Moniteur ProducteurConsommateur { //variable conditionnelle pour non plein et non vide bool nplein, nvide ; int compteur =0, ic=0, ip=0 ;

6.11. CHANGES DE MESSAGES

31

// section critique pour le dpt void mettre (int objet) { if (compteur==N) wait(nplein) ; //attendre jusqu ce que le tampon devienne non plein // dposer un objet dans le tampon tampon[ip] = objet ; ip = (ip+1)%N ; compteur++ ; // si le tampon tait vide avant le dpt, // envoyer un signal pour rveiller le consommateur. if (compteur==1) signal(nvide) ; } // section critique pour le retrait void retirer (int* objet) { if (compteur ==0) wait(nvide) ; objet = tampon[ic] ; compteur -- ; if(compteur==N-1) signal(nplein) ; } }

6.11 changes de messages


La synchronisation entre processus peut tre ralise au moyen dchange de messages, dans Unix System V. Un processus peut attendre quun autre lui envoie un message. Lavantage principale est quil ny a pas de partage despace de donnes, et donc il ny aura pas de conit daccs. Comme problmes, on peut signaler que les messages envoys peuvent se perdre. Lorsquun metteur envoie un message, il attend pendant un certain dlai la conrmation de rception du message par le rcepteur (un acquittement). Si, au bout de ce dlai, il ne reoit pas lacquittement, il renvoie le message. Il peut y avoir des messages en double en cas de perte du message dacquittement. Pour viter cela, on associe chaque message une identication unique. Lauthentication des messages se fait alors par lutilisation des cls. Le mecanisme detaill dchange de messages sera tudi dans le Chapitre ??, Communications IPC System V.

32

CHAPITRE 6. SYNCHRONISATION DES PROCESSUS

6.12 Producteur/consommateur par tubes


Le producteur et le consommateur communiquent au moyen dun tube de communication nomm. Le producteur dpose ses objets un un dans le tube. Il se bloque lorsquil tente de dposer un objet et le tube na pas sufsamment de place. Il est rveill quand le tube a sufsamment despace libre. De son ct, le consommateur rcupre un un des objets du tube. Il se bloque lorsquil tente de rcuprer un objet partir dun tube vide. Il est rveill lorsque le tube devient non vide. Le schma de programme p-c-pipe.c montre lutilisation de cette technique.

Listing 6.9 p-c-pipe.c


# include < s t d i o . h> # include < u n i s t d . h> i n t main ( void ) { int fil des [ 2 ] ; char c ;

// pipe pour s y n c h r o n i s e r // c a r a c t r e pour s y n c h r o n i s e r // n c e s s a i r e pour e n t r e r dans l a // s e c t i o n c r i t i q u e l a premire f o i s // p r o c e s s u s f i l s

10

pipe ( f i l d e s ) ; write ( f i l d e s [ 1 ] , & c , 1 ) ;

20

30

i f ( fork ( ) = = 0 ) { for ( ; ; ) { read ( f i l d e s [ 0 ] , & c , 1 ) ; // e n t r e s e c t i o n c r i t i q u e // < Section c r i t i q u e > w r i t e ( f i l d e s [ 1 ] , & c , 1 ) ; // q u i t e r s e c t i o n c r i t i q u e } } e l s e // p r o c e s s u s pre { for ( ; ; ) { read ( f i l d e s [ 0 ] , & c , 1 ) ; // e n t r e s e c t i o n c r i t i q u e // < section c r i t i q u e > w r i t e ( f i l d e s [ 1 ] , & c , 1 ) ; // q u i t t e r s e c t i o n c r i t i q u e } } return 0 ;

6.13. EXCLUSION MUTUELLE (MUTEX) DE THREADS

33

6.13 Exclusion mutuelle (mutex) de threads


Une synchronisation correcte entre plusieurs ls dexcution est trs importante. Pour accder des donnes globales partages, il est indispensable de mettre en uvre un mechanisme permettant de protger une variable partage par plusieurs threads. Ce mcanisme est appel mutex (MUTual EXclusion ) et repose sur lutilisation du type pthread_mutex_t. Ainsi une variable sert de verrou une zone de mmoire globale particulire. Les appels dexclusion mutuelle de threads consistent notamment en linitialisation, le verrouillage, le dverrouillage et la destruction. Initialisation On peut initialiser un mutex statique ou dynamique. Ainsi on peut initialiser un mutex statiquement comme : phtread_mutex_t mutex = PHTREAD_MUTEX_INITIALIZER; ou en utilisant linitialisation dynamique avec lappel systme : int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attributs); Avec les dclarations : phtread_mutex_t mutex; phtread_mutexattr_t mutexattr; ... phtread_mutex_init(&mutex, &mutexattr); ... Verrouillage Lappel systme de verrouillage des threads est : phtread_mutex_lock(phtread_mutex_t *mutex);

34 Dverrouillage

CHAPITRE 6. SYNCHRONISATION DES PROCESSUS

Lappel systme de dverrouillage des threads est : phtread_mutex_unlock(phtread_mutex_t *mutex);

Exemple 7. Le programme sum.c somme squentiellement N nombres. Listing 6.10 sum.c


# i n c l u d e < s t d i o . h> main ( i n t a r g c , char { double sum ; int N, i ;

!

argv )

10

N = a t o i ( argv [ 1 ] ) ; // Nombre sum = 0 ; // somme f o r ( i = 1 ; i <=N; + + i ) { sum + = i ; } p r i n t f ( "Somme = % 1 0 . 0 f \n " , sum ) ; return ( 0 ) ;

Le programme sum-thread.c somme les mmes N nombres, mais en forme parallle avec deux threads qui utilisent mutex.

Listing 6.11 sum-thread.c


# i n c l u d e < s t d i o . h> # i n c l u d e < pthread . h> v o l a t i l e double somme = 0 . 0 ; pthread_mutex_t somme_lock ; v o l a t i l e double N ; // somme ( shared ) // Lock // N

10

void  p r o c e s s u s ( void  arg ) { double localsum ; int i ; i n t i p r o c = (  ( ( char  ) arg ) 0 ) ; localsum = 0 ; // somme f o r ( i = i p r o c ; i <=N ; i +=2) { localsum + = i ;

6.13. EXCLUSION MUTUELLE (MUTEX) DE THREADS


} // Lock de somme , c a l c u l e r e t dbloquer pthread_mutex_lock(&somme_lock ) ; somme + = localsum ; pthread_mutex_unlock (&somme_lock ) ; r e t u r n (NULL) ;

35

20 }

i n t main ( i n t a r g c , char ! argv ) { pthread_t thread0 , thread1 ; void  r e t v a l ; 30 N = a t o i ( argv [ 1 ] ) ; // Nombre // I n i t i a l i s e r l e l o c k de l a somme p t h r e a d _ m u t e x _ i n i t (&somme_lock , NULL) ; // 2 threads i f ( p t h r e a d _ c r e a t e (& t h r e a d 0 , NULL , p r o c e s s u s , " 0 " ) | | p t h r e a d _ c r e a t e (& t h r e a d 1 , NULL , p r o c e s s u s , " 1 " ) ) { p r i n t f ( "%s : e r r e u r t h r e a d\n " , argv [ 0 ] ) ; exit (1); } // J o i n l e s deux t h r e a d s i f ( pthread_join ( thread0 , & r e t v a l ) || pthread_join ( thread1 , & r e t v a l ) ) { p r i n t f ( "%s : e r r e u r j o i n \n " , argv [ 0 ] ) ; exit (1); } p r i n t f ( "Somme ( p a r t a g e ) = % 1 0 . 0 f \n " , somme ) ; return 0 ;

40

Exemple 8. Soit un tableau M de N lments rempli par un thread lent et lu par un autre plus rapide. Le thread de lecture doit attendre la n du remplissage du tableau avant dafcher son contenu. Les mutex peuvent protger le tableau pendant le temps de son remplissage.

Listing 6.12 mutex-threads2.c


# include < s t d i o . h> # include < pthread . h> # define N 5 s t a t i c pthread_mutex_t mutex ;

36
i n t M[N] ;

CHAPITRE 6. SYNCHRONISATION DES PROCESSUS

10

void  l e c t u r e ( void { int i ;

arg )

pthread_mutex_lock(&mutex ) ; f o r ( i = 0 ; i < N ; i ++) p r i n t f ( " L c t u r e : M[%d ] = % d\n " , i , M[ i ] ) ; pthread_mutex_unlock (&mutex ) ; pthread_exit ( 0 ) ; } 20 void  e c r i t u r e ( void { int i ;

arg )

30 }

pthread_mutex_lock(&mutex ) ; f o r ( i = 0 ; i < N ; i ++) { M[ i ] = i ; p r i n t f ( " c r i t u r e : M[%d ] = % d\n " , i , M[ i ] ) ; s l e e p ( 2 ) ; // R a l e n t i r l e t h r e a d d e c r i t u r e } pthread_mutex_unlock (&mutex ) ; pthread_exit ( 0 ) ; i n t main ( void ) { p t h r e a d _ t th1 , th2 ; void  r e t ;

40

p t h r e a d _ m u t e x _ i n i t (&mutex , NULL) ; p t h r e a d _ c r e a t e (& th1 , NULL , e c r i t u r e , NULL) ; p t h r e a d _ c r e a t e (& th2 , NULL , l e c t u r e , NULL ) ; p t h r e a d _ j o i n ( th1 , & r e t ) ; p t h r e a d _ j o i n ( th2 , & r e t ) ; return 0 ;

Nous montrons ensuite une excution de mutex-threads2.c :


leibnitz> gcc -o mt2 mutex-threads2.c -lpthread leibnitz> mt2 criture: M[0] = 0

6.14. SMAPHORES AVEC SYSTEM V (SOLARIS)


criture: M[1] = 1 criture: M[2] = 2 criture: M[3] = 3 criture: M[4] = 4 Lcture: M[0] = 0 Lcture: M[1] = 1 Lcture: M[2] = 2 Lcture: M[3] = 3 Lcture: M[4] = 4 leibnitz>

37

Sans lutilisation des mutex, probablement on obtiendrait des sorties rrones comme la suivante :
criture: M[0] = 0 Lcture: M[0] = 0 Lcture: M[1] = 0 Lcture: M[2] = 0 Lcture: M[3] = 0 Lcture: M[4] = 0 criture: M[1] = 1 criture: M[2] = 2 criture: M[3] = 3 criture: M[4] = 4

6.14 Smaphores avec System V (Solaris)


Le Unix System V sur Solaris permet de crer et dutiliser les smaphores. Les fonctions de manipulation des smaphores sont dans la librairie <synch.h>. Le type smaphore est dsign par le mot sema_t. Linitialisation dun smaphore est ralise par lappel systme :
#include <synch.h> int sema_init(sema_t*sp, unsigned int count, int type, NULL);

o sp est un pointeur sur le smaphore initialiser ; count est la valeur initiale du smaphore ; type indique si le smaphore est utilis pour synchroniser des processus lgers (threads) ou des processus. Le type peut tre USYNC_PROCESS ou bien USYNC_THREAD. Par dfaut, le type sera USYNC_THREAD. int sema_wait (sema_t *sp) est quivalente lopration P.

38

CHAPITRE 6. SYNCHRONISATION DES PROCESSUS


int sema_post(sema_t *sp) est quivalente lopration V. int sema_trywait(sema_t *sp) dcrmente la valeur du smaphore sp si sa valeur est suprieure 0 ; sinon elle retourne une erreur. Cest une opration atomique.

Exemple 9. Usage de smaphores System V. Listing 6.13 v-semaphore.c


# i n c l u d e < synch . h > // smaphores # 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 > // p r i n t f # define val 1 sema_t mutex ; // smaphore i n t var_glob = 0 ; void  increment ( void  ) ; void  decrement ( void  ) ; i n t main ( ) { // i n i t i a l i s e r mutex s e m a _ i n i t (&mutex , v a l ,USYNC_THREAD,NULL ) ; p r i n t f ( " i c i main : var_glob = %d\n " , var_glob ) ; // c r a t i o n d un t h r e a d pour increment t h r _ c r e a t e (NULL, 0 , increment ,NULL, 0 , NULL) ; // c r a t i o n d un t h r e a d pour decrement t h r _ c r e a t e (NULL, 0 , decrement ,NULL, 0 , NULL) ; // a t t e n d r e l a f i n des t h r e a d s 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 t h r e a d s : var_glob =%d \ n " , var_glob ) ; return 0 ; } void  decrement ( void  ) { i n t nb = 3 ; // a t t e n d r e l a u t o r i s a t i o n d a c c s sema_wait (&mutex ) ; while ( nb  ) { var_glob = 1 ; p r i n t f ( " i c i s c de decrement : var_glob=%d\n " , var_glob ) ; } sema_post (&mutex ) ; r e t u r n ( NULL) ; }

10

20

30

6.14. SMAPHORES AVEC SYSTEM V (SOLARIS)


40 void  increment ( void  ) { i n t nb = 3 ; sema_wait (&mutex ) ; while ( nb  ) { var_glob + = 1 ; p r i n t f ( " i c i s c de increment : var_glob = %d\n " , var_glob ) ; } sema_post (&mutex ) ; r e t u r n ( NULL) ; }

39

50

Excution du programme v-semaphore.c sur la machine Unix nomm jupiter :


jupiter% gcc v-semaphore.c -lthread -o mutex jupiter% mutex ici main : var_glob = 0 ici sc de increment : var_glob = 1 ici sc de increment : var_glob = 2 ici sc de increment : var_glob = 3 ici sc de decrement : var_glob = 2 ici sc de decrement : var_glob = 1 ici sc de decrement : var_glob = 0 ici main, fin threads : var_glob = 0

Exemple 10. Problme du Producteur/consommateur. Listing 6.14 v-prod-cons.c


# include # include # include # include # define < synch . h> < u n i s t d . h > // s l e e p < t h r e a d . h> < s t d i o . h> taille 3

10

sema_t p l e i n , vide , mutex ; i n t tampon [ t a i l l e ] ; void  consommateur ( void  ) ; void  producer ( void  ) ; i n t main ( ) {

40

CHAPITRE 6. SYNCHRONISATION DES PROCESSUS


// i n i t i l i s e r l e s smaphores s e m a _ i n i t (& p l e i n , 0 , USYNC_THREAD,NULL) ; s e m a _ i n i t (& vide , t a i l l e ,USYNC_THREAD,NULL) ; s e m a _ i n i t (&mutex , 1 , USYNC_THREAD,NULL) ; // c r e r l e s t h r e a d s t h r _ c r e a t e (NULL, 0 , consommateur ,NULL, 0 , NULL) ; t h r _ c r e a t e (NULL, 0 , producer ,NULL, 0 , NULL ) ; // a t t e n d r e l a f i n des t h r e a d s while ( ( t h r _ j o i n (NULL,NULL,NULL) = = 0 ) ) ; p r i n t f ( " f i n des t h r e a d \ n " ) ; return 0 ;

20

30

40

void  consommateur ( void  r e t r a i t ) { i n t i c = 0 , nbcons = 0 , o b j e t ; do { sema_wait (& p l e i n ) ; sema_wait (&mutex ) ; // consommer o b j e t = tampon [ i c ] ; sema_post (&mutex ) ; sema_post (& vide ) ; p r i n t f ( "\n i c i cons . : tampon[%d]= %d\n " , i c , objet ) ; i c =( i c +1)% t a i l l e ; nbcons ++; sleep ( 2 ) ; } while ( nbcons < = 5 ) ; r e t u r n ( NULL) ; } void  producer ( void  ) { i n t i p = 0 , nbprod = 0 , o b j e t = 0 ; do { sema_wait (& vide ) ; sema_wait (&mutex ) ; // produire tampon [ i p ]= o b j e t ; sema_post (&mutex ) ; sema_post (& p l e i n ) ; p r i n t f ( "\n i c i prod . : tampon[%d]= %d\n " , i p , objet ) ; o b j e t ++; nbprod ++; i p =( i p +1)% t a i l l e ;

50

60

6.14. SMAPHORES AVEC SYSTEM V (SOLARIS)


} while ( nbprod < = 5 ) ; r e t u r n NULL;

41

Excution de producteurs et consommateurs :


jupiter% gcc prod_cons.c -lthread o- prod_cons jupiter% prod_cons ici prod. : tampon[0]= 0 ici prod. : tampon[1]= 1 ici prod. : tampon[2]= 2 ici cons. :tampon[0]= 0 ici prod. : tampon[0]= 3 ici cons. :tampon[1]= 1 ici prod. : tampon[1]= 4 ici cons. :tampon[2]= 2 ici prod. : tampon[2]= 5 ici cons. :tampon[0]= 3 ici cons. :tampon[1]= 4 ici cons. :tampon[2]= 5 fin des thread jupiter%

Exemple 11. Problme des philosophes. Listing 6.15 v-philosophe.c


# include # include # include # include < stdio . h> < unistd . h> < thread . h> < synch . h > // // // // printf sleep threads smaphores

10

# define N 5 // nombre de p h i l o s o p h e s # d e f i n e G (N+ i 1)%N // philosophe gauche de i # d e f i n e D ( i +1)%N // philosophe d r o i t e de i # define l i b r e 1 # d e f i n e occupe 0 i n t fourch [N] = { l i b r e , l i b r e , l i b r e , l i b r e , l i b r e } ; sema_t mutex ; void  philosophe ( void  ) ; i n t main ( ) { i n t NumPhi [N ] = { 0 , 1 , 2 , 3 , 4 } ; s e m a _ i n i t (&mutex , 1 , NULL , NULL) ; // c r a t i o n des N p h i l o s o p h e s

42
20

CHAPITRE 6. SYNCHRONISATION DES PROCESSUS


f o r ( i n t i = 0 ; i <N ; i ++) t h r _ c r e a t e (NULL, 0 , philosophe , & (NumPhi [ i ] ) , 0 , NULL) ; // a t t e n d r e l a f i n des t h r e a d s while ( ( t h r _ j o i n (NULL,NULL,NULL) = = 0 ) ) ; p r i n t f ( " f i n des t h r e a d s \n " ) ; return 0 ;

30

40

50

// La f o n c t i o n de chaque philosophe void  philosophe ( void  num) { i n t i =  ( i n t  ) num ; i n t nb = 2 ; while ( nb ) { // penser sleep ( 1 ) ; // e s s a y e r de prendre l e s f o u r c h e t t e s pour manger sema_wait (&mutex ) ; i f ( fourch [G] & & fourch [ i ] ) { fourch [G ] = 0 ; fourch [ i ] = 0 ; sema_post (&mutex ) ; nb  ; // manger p r i n t f ( " philosophe [%d ] mange\n " , i ) ; s l e e p ( 1 ) ; // manger p r i n t f ( " philosophe [%d ] a f i n i de manger\n " , i ) ; // l i b r e r l e s f o u r c h e t t e s sema_wait (&mutex ) ; fourch [G ] = 1 ; fourch [ i ] = 1 ; sema_post (&mutex ) ; } e l s e sema_post (&mutex ) ; } }

Excution de la premire version des philosophes :


jupiter% v-philosophe.c -lthread -o philosophe jupiter% philosophe philosophe[0] mange philosophe[2] mange philosophe[0] a fini de manger philosophe[2] a fini de manger philosophe[4] mange

6.14. SMAPHORES AVEC SYSTEM V (SOLARIS)


philosophe[1] mange philosophe[4] a fini philosophe[4] mange philosophe[1] a fini philosophe[1] mange philosophe[4] a fini philosophe[3] mange philosophe[1] a fini philosophe[3] a fini philosophe[2] mange philosophe[0] mange philosophe[0] a fini philosophe[2] a fini philosophe[3] mange philosophe[3] a fini fin des threads jupiter%

43

de manger de manger de manger de manger de manger

de manger de manger de manger

Exemple 12. Solution amliore des Philosophes (Solaris) version 2 Listing 6.16 v-philosophe2.c
# i n c l u d e < s t d i o . h> # i n c l u d e < u n i s t d . h> # i n c l u d e < t h r e a d . h> # i n c l u d e < synch . h> # define N 5 # d e f i n e G (N+ i 1)%N # d e f i n e D ( i +1)%N

// philosophe // philosophe

gauche de i d r o i t e de i

10

enum e t a t { penser , faim , manger } ; i n t fourch [N] = { l i b r e , l i b r e , l i b r e , l i b r e , l i b r e } ; e t a t E t a t [N] = { penser , penser , penser , penser , penser } ; sema_t S [N ] ; sema_t mutex ; void  philosophe ( void  ) ; i n t main ( ) { i n t i , NumPhi [N ] = { 0 , 1 , 2 , 3 , 4 } ; s e m a _ i n i t (&mutex , 1 , NULL , NULL ) ; f o r ( i = 0 ; i <N ; i ++) sema_int (&S [ i ] , 0 , NULL,NULL ) ; // c r a t i o n des N p h i l o s o p h e s f o r ( i = 0 ; i <N ; i ++) t h r _ c r e a t e (NULL, 0 , philosophe , & (NumPhi [ i ] ) , 0 , NULL) ; // a t t e n d r e l a f i n des t h r e a d s

20

44

CHAPITRE 6. SYNCHRONISATION DES PROCESSUS


while ( ( t h r _ j o i n (NULL,NULL,NULL) = = 0 ) ) ; p r i n t f ( " f i n des t h r e a d s \ n " ) ; return 0 ;

} 30

40

50

void  philosophe ( void  num) { i n t i =  ( i n t  ) num ; i n t nb = 2 ; while ( nb ) { nb  ; // penser sleep ( 1 ) ; // t e n t e r de manger sema_wait (&mutex ) ; E t a t [ i ]= faim ; test ( i ) ; sema_post (&mutex ) ; sema_wait (&S [ i ] ) ; // manger p r i n t f ( " philosophe [%d ] mange\n " , i ) ; s l e e p ( 1 ) ; // manger p r i n t f ( " philosophe [%d ] a f i n i de manger\n " , i ) ; // l i b r e r l e s f o u r c h e t t e s sema_wait (&mutex ) ; E t a t [ i ] = penser ; // v r i f i e r s i s e s v o i s i n s peuvent manger T e s t (G ) ; T e s t (D ) ; sema_post (&mutex ) ; } } // procdure qui v r i f i e s i l e philosophe i peut manger void T e s t ( i n t i ) { i f ( ( E t a t [ i ] = = faim ) & & ( E t a t [G ] ! = manger ) && ( E t a t [D] ! = manger ) ) { E t a t [ i ] = manger ; sema_post (&S [ i ] ) ; } }

60

Excution de la deuxime version des philosophes :


jupiter% v-philosophe2.c -lthread -o philosophe2 jupiter% philosophe2

6.14. SMAPHORES AVEC SYSTEM V (SOLARIS)


philosophe[0] mange philosophe[2] mange philosophe[0] a fini philosophe[2] a fini philosophe[4] mange philosophe[1] mange philosophe[4] a fini philosophe[3] mange philosophe[1] a fini philosophe[0] mange philosophe[0] a fini philosophe[1] mange philosophe[3] a fini philosophe[4] mange philosophe[4] a fini philosophe[3] mange philosophe[1] a fini philosophe[3] a fini philosophe[2] mange philosophe[2] a fini fin des threads jupiter%

45

de manger de manger

de manger de manger de manger de manger de manger de manger de manger de manger

46

CHAPITRE 6. SYNCHRONISATION DES PROCESSUS

6.15 Exercises
1. Pourquoi le partage de donnes pose des problmes dans un systme multiprogramm en temps partag ? 2. Le systme Unix permet-il de contrler les accs aux donnes partages ? 3. Quest-ce quune section critique ? 4. Expliquez la raison pour laquelle il est ncessaire de synchroniser les processus. Est-ce que tous les types de SE utilisent la synchronisation de processus ? 5. Identiez et expliquez un problme typique qui ncessiterait la synchronisation de processus. 6. Identiez le point (i.e. les noncs) dun processus qui reprsente sa section critique. 7. Identiez le problme principal qui peut survenir lorsque laccs la section critique dun processus est contrl par une variable boolenne de type verrou. Donnez une solution ce problme. 8. Quel est le problme principal que rsout un smaphore en ce qui concerne laccs une section critique ? 9. Quand doit-on utiliser des smaphores binaire et gnral ? Peut-on utiliser ces deux types de smaphores dans un mme processus et pour un mme problme de synchronisation ? Expliquez. 10. Pourquoi un tube Unix utilise gnralement deux canaux de communication entre deux processus, soit un canal dentre et un canal de sortie ? Existe-il des alternatives ce mode de communication utilisant les tubes Unix ? 11. Considrez lapplication suivante qui implante un systme interactif qui lit dans une boucle des caractres entrs via le clavier et qui les afche lcran. Le traitement se termine lorsque lusager presse la touche <Ctrl-d> pour insrer le caractre de n de chier (EOF) dans le tampon.
// Processus parent char *tampon; // tampon partag par les processus int i=0, j=0; // positions de lecture et dcriture dans le tampon // Processus lecture ...

6.15. EXERCISES
while (read(stdin, tampon, 1) != NULL) ... // Processus criture ... while (write(stdout, tampon, 1) != NULL) ...

47

(a) Synchronisez la communication entre les processus lecture et criture laide de tubes et dappels systme fork. (b) Synchronisez la communication entre les threads lecture et criture laide de smaphores. (c) Considrez la solution suivante au problme daccs des ressources partages en mode de lecture et dcriture. Les processus en lecture et en criture sont en comptition pour laccs une ressource partage. Un seul processus la fois peut tre dans sa section critique, ceci est valable autant pour les processus en lecture et en criture. Le systme ne fait pas de diffrence entre les processus en lecture et en criture, et chaque processus peut postuler une requte daccs la ressource qui lui sera accorde ds quun processus a ni de lire ou dcrire sur la ressource. Cependant, le systme dnit des processus prioritaires pour lcriture de la ressource, et les requtes de ces processus doivent tre honores avant celles des processus normaux en lecture et en criture ds que la section critique se libre. Montrez la synchronisation des processus en lecture et en criture, en utilisant des smaphores. 12. Considrez le buffer circulaire montr sur la gure 6.9, qui rpresente une variation du problme du producteur/consommateur. Rsoudre le problme avec des smaphores. 13. Le code suivant contient une solution propose au problme daccs une section critique. Expliquez pourquoi cette solution serait correcte ou incorrecte, selon quelle respecte les 4 principes fondamentaux rgissant laccs une section critique.
/* Variables partages par les processus P0 et P1. */ int flag[2]; // Tableau de boolens stockant 0 ou 1 // Variable indiquant le processus actif, // i.e. 0 pour P0 ou 1 pour P1

48

CHAPITRE 6. SYNCHRONISATION DES PROCESSUS

Producteur

Consomateur

F IG . 6.9 Buffer circulaire.

int turn; // Code du processus P0 do { flag[0] = 1; // Mettre le flag de P0 1 while (turn != 0) // Si P0 est inactif alors, ... { while (flag[1]) // Attendre linactivit de P1 { /* skip */ } turn = 0; // Indique que P0 est maintenant actif } ... <Section critique pour le processus P0> ... // Rinitialiser le flag de P0 afin que P1 puisse // sactiver dans sa section critique flag[0] = 0; ... <Autres noncs pour P0> ... } while (1); /* Code du processus P1 */ do { flag[1] = 1; /* Mettre le flag de P1 1 */ while (turn != 1) /* Si P1 est inactif alors, ... */ { while (flag[0]) /* Attendre linactivit de P0 */ { /* skip */ }

6.15. EXERCISES

49

turn = 1; /* Indiquez que P1 est maintenant actif */ } ... <Section critique pour le processus P1> ... // Rinitialiser le flag de P1 afin que P0 puisse // sactiver dans sa section critique flag[1] = 0; ... <Autres noncs pour P1> ... } while (1);

14. Dijkstra a propos les 3 solutions suivantes comme tant des solutions logicielles au problme daccs une section critique. Par contre, aux vues de Dijkstra ces solutions ntaient pas tout fait correctes. Alors, expliquez pourquoi chacune des solutions est effectivement incorrecte. a ) int turn;
proc(int i) { while (TRUE) { compute; while (turn != i); <section critique> turn = (i+1) mod 2; } } turn = 1; fork(proc, 1, 0); fork(proc, 1, 1);

b ) boolean flag[2];
proc(int i) { while (TRUE) { compute; while (flag[(i+1) mod 2]); flag[i] = TRUE; <section critique> flag[i] = FALSE; } } flag[0] = flag[1] = FALSE; fork(proc, 1, 0); fork(proc, 1, 1);

c ) boolean flag[2];

50

CHAPITRE 6. SYNCHRONISATION DES PROCESSUS


proc(int i) { while (TRUE) { compute; flag[i] = TRUE; while (flag[(i+1) mod 2]); <section critique> flag[i] = FALSE; } } flag[0] = flag[1] = FALSE; fork(proc, 1, 0); fork(proc, 1, 1);

15. Considrez une route deux voies (une voie par direction) et oriente selon un axe nord-sud. Cette route traverse un tunnel qui ne possde quune seule voie, et par consquent son accs doit tre partag en temps par les voitures provenant de chacune des deux directions (nord ou sud). An de prvenir les collisions, des feux de circulation ont t installs chacune des extrmits du tunnel. Un feu vert indique quil ny a aucune voiture dans le tunnel provenant de la direction oppose, et un feu rouge indique quil y a une voiture contresens dans le tunnel. Le trac dans le tunnel est contrl par un ordinateur central qui est prvenu de larrive de vhicules par des senseurs poss dans chaque direction. Lorsquun vhicule approche le tunnel en direction nord ou sud, le senseur externe excute la fonction arrive(direction) pour prvenir lordinateur central. Similairement, lorsquune voiture sort du tunnel le senseur interne excute la fonction dpart (direction). Concevez une esquisse de code pour synchroniser laccs au tunnel an que les feux de circulation fonctionnent correctement dans les deux directions. 16. On a 3 processus  , chaque processus #%$'&

 et " concurrents. Le code indpendant )(1032!03465 est le suivant :

de

Pi() // Processus Pi avec i=1,2,3 { while(TRUE) printf("je suis le processus" i ); }

On veut montrer lcran la sortie suivante :


je suis le processus 1 je suis le processus 2 je suis le processus 3

6.15. EXERCISES
je suis le processus 1 je suis le processus 2 je suis le processus 3 ...

51

Synchroniser les 3 processus laide des smaphores binaires. 17. Le programme suivant permet de calculer une estimation de la valeur de 7 avec un ot unique dexcution. Paralllisez ce programme laide dexclusion mutuelle de threads et en respectant lesprit de la boucle for (i=0 ; i<intervals ; ++i){...}.
#include <stdlib.h> #include <stdio.h> int main(int argc, char **argv) { double width, sum; int intervals, i; // nombre dintervalles intervals = atoi(argv[1]); width = 1.0 / intervals; // calcul de pi sum = 0; for (i=0; i<intervals; ++i) { double x = (i + 0.5) * width; sum += 4.0 / (1.0 + x * x); } sum *= width; printf("Estimation de pi = %10.9f\n", sum); return(0); }