ED 1 : Synchronisation à l aide des sémaphores et des moniteurs Posix Samia Bouzefrane Laboratoire CEDRIC Conservatoire National des Arts et Métiers http://cedric.cnam.fr/~bouzefra 1
Partie I : Problème des philosophes Le paradigme des philosophes et des spaghettis a été posé la première fois par Dijkstra en 1971. Cinq philosophes sont réunis autour d'une table pour philosopher. Mais comme leur réflexion intense nécessite une alimentation en rapport, on a disposé un grand plat de spaghetti au milieu de la table et une assiette et une fourchette devant chaque philosophe. Ces philosophes respectent rigoureusement un rituel qui impose de manger les spaghettis avec deux fourchettes et non avec une seule. Il aurait donc fallu déposer sur la table 10 fourchettes et non 5. Il y a là un problème de partage de ressources (les fourchettes) et les philosophes conviennent du protocole suivant: après une période de réflexion, un philosophe qui désire manger ne peut le faire que si les deux fourchettes, celle à sa gauche et celle à sa droite, les seules dont il peut se servir, sont toutes les deux libres. A un instant donné, seuls deux philosophes peuvent être en train de manger simultanément. Bien entendu, un philosophe qui mange s'arrête de manger au bout d'un temps fini. On peut assimiler chaque philosophe à un processus cyclique: Répéter Penser; Prologue; Manger section critique; Epilogue; Jusqua Faux; Une solution basée sur les sémaphores Posix serait la suivante : Question 1 : Quel est le problème qui peut se poser dans cette solution? Vérifiez-le en exécutant le programme correspondant sachant que la compilation et l exécution d un programme C se fait comme suit : $gcc Philo.c o Philo lpthread $./Philo Solution 1 /* PhiloSem.c avec des semaphores et des threads*/ #include <stdio.h> #include <semaphore.h> // declaration des semaphores // ligne 1: sem_t Fourchette[5]; void *Philosophe(void * t) int j; int i=(int) t; srand(pthread_self()); for(j=0; j<20; j++) printf("philosophe %d pense \n",i); sleep(rand()%3); printf("philosophe %d veut manger \n",i); // ligne 2 : sem_wait(&fourchette[i]); sem_wait(&fourchette[(i+1)%5]); 2
printf("philosophe %d est en train de manger avec Fourch %d et Fourch %d \n", i, i, (i + 1)%5); sleep(rand()%5); sem_post(&fourchette[i]); sem_post(&fourchette[(i+1)%5]); // ligne 3: printf("philosophe %d libere les fourchettes \n", i, i, (i+1)%5); pthread_exit (0); int main(void) pthread_t th[5]; int i; /* creation et initialisation des semaphores */ // ligne 4: for (i=0; i<5; i++) sem_init(&fourchette[i], 0, 1); /* creation des threads */ for (i=0; i<5; i++) pthread_create (&th[i], 0, (void *(*)())Philosophe,(void *)i); /* attente de terminaison */ for (i=0; i<5; i++) pthread_join (th[i], NULL); /* suppression des semaphores */ //ligne 5 for (i=0; i<5; i++) sem_destroy(&fourchette[i]); return (0); Question 2 : On complète le programme précédent avec les lignes suivantes : ligne 1 : sem_t Antichambre ; ligne 2 : sem_wait(&antichambre); ligne 3 : sem_post(&antichambre); ligne 4 : sem_init(&antichambre, 0, 4); ligne 5 : sem_destroy(&antichambre) ; A quoi servent ces lignes? Expliquez comment cela permet de résoudre le problème précédent. Vérifiez-le en exécutant le programme. Question 3 : Une solution du même problème qui utilise les moniteurs Posix, figure à l URL suivante : http://cedric.cnam.fr/~bouzefra/livre/chapitre8/philo.c. Quel est le problème qui peut se poser dans cette solution. Téléchargez ce programme et exécutez-le. 3
Partie II : Allocation de ressources Nous nous intéressons au problème d allocation de ressources banalisées. Nous supposons disposer dans un système de plusieurs exemplaires d une même ressource, dont un nombre quelconque peut être demandé par un processus à un moment donné. Pour simplifier, nous considérons un seul type de ressource. Tout processus actif qui demande des exemplaires de cette ressource, doit solliciter les services d un allocateur via deux procédures : - request(m) : pour demander l allocation de m exemplaires de la ressource - release(m) : pour libérer les m exemplaires de la ressource L allocateur maintient une variable critique qui compte le nombre d exemplaires disponibles afin de satisfaire éventuellement de nouvelles demandes. Contexte commun : entier NbRessDisponibles :=Max ; Comportement d un processus : While (true) request(m) ; utiliser les m ressources release(m) ; Procédure request (entier m) Tant que (NbRessDisponibles<m) bloquer le processus appelant ; NbRessDisponibles = NbRessDisponibles m ; // allocation des m ressources Procédure release(entier m) // libération des m ressources NbRessDisponibles = NbRessDisponibles +m ; Réveiller des processus qui seraient bloqués Notons que dans cette solution, plusieurs processus demandeurs peuvent être servis simultanément s il y a suffisamment de ressources. De plus, la procédure release doit réveiller tous les processus en attente pour trouver ceux pour lesquels les demandes peuvent être satisfaites. Question Complétez le programme C suivant qui utilise des moniteurs Posix. Notons qu il existe une primitive qui permet de réveiller tous les processus à la fois en attente derrière une condition : int pthread_cond_broadcast(pthread_cond_t *condition); Le programme obtenu doit être compilé avec l option -lpthread. /* allocation.c */ #include <stdio.h> #include <pthread.h> #define NbTh 5 ressources */ #define M 100 /*Nombre de threads symbolisant les demandeurs de 4
pthread_t tid[nbth]; pthread_mutex_t mutex; pthread_cond_t condressnondispo; // variable critique int NbRessDisponibles=M; void request(int m) // a completer void release(int m) // a completer void * Demandeur(void ) int j; srand(pthread_self()); j=rand()%100; request(j); /* temps d'utilisation */ printf("le demandeur %d utilise les ressources\n", pthread_self()); usleep(rand()%200000); release(j); int main() int num; // initialiser les mutex et conditions //creation des threads pthread_create(tid+num,0,(void *(*)())Demandeur,NULL ); //attend la fin de toutes les threads pthread_join(tid[num],null); // detruire les resources allouees return 0; 5
Solution Exercice 1 Question 1 : La solution telle qu elle est donnée peut présenter une situation d interblocage. Chaque philosophe i détient une fourchette i et demande la fourchette i+1 détenue par le philosophe i+1. Question 2 : La solution améliorée est une solution équitable et a été proposée par Ben Ari. Elle rajoute un sémaphore qui symbolise une antichambre par laquelle doivent passer les philosophes pour faire leur demande. Cette antichambre ne peut contenir au plus que 4 philosophes. Ce qui permet dans le pire cas d avoir au moins un philosophe parmi les cinq (s ils sont tous demandeurs) qui obtient les 2 fourchettes. Pas d interblocage dans cette solution, de plus grâce à l antichambre les demandeurs seront servis dans l ordre. Question 3 : La solution des philosophes basée sur les moniteurs ne présente pas de situation d interblocage mais peut générer de la famine parce que ce sont toujours les mêmes philosophes qui sont servis. Exercice 2 Question 2 /* allocation.c */ #include <stdio.h> #include <pthread.h> #define NbTh 5 ressources */ #define M 100 /*Nombre de threads symbolisant les demandeurs de pthread_t tid[nbth]; pthread_mutex_t mutex; pthread_cond_t condressnondispo; // variable critique int NbRessDisponibles=M; void request(int m) pthread_mutex_lock(&mutex); while(nbressdisponibles < m) pthread_cond_wait(&condressnondispo, &mutex); printf("le demandeur %d alloue %d ressources\n", pthread_self(),m); NbRessDisponibles = NbRessDisponibles - m; printf("le nb de ress disponibles %d \n", NbRessDisponibles); pthread_mutex_unlock(&mutex); void release(int m) pthread_mutex_lock(&mutex); printf("le demandeur %d libere %d ressources\n",pthread_self(),m); NbRessDisponibles = NbRessDisponibles + m; 6
printf("le nb de ress disponibles %d \n", NbRessDisponibles); pthread_cond_broadcast(&condressnondispo); pthread_mutex_unlock(&mutex); void * Demandeur(void ) int j; srand(pthread_self()); j=rand()%100; request(j); /* temps d'utilisation */ printf("le demandeur %d utilise les ressources\n", pthread_self()); usleep(rand()%200000); release(j); int main() int num; pthread_mutex_init(&mutex,0); pthread_cond_init(&condressnondispo,0); //creation des threads pthread_create(tid+num,0,(void *(*)())Demandeur,NULL ); //attend la fin de toutes les threads pthread_join(tid[num],null); pthread_mutex_destroy(&mutex); pthread_cond_destroy(&condressnondispo); return 0; 7