La présentation est en train de télécharger. S'il vous plaît, attendez

La présentation est en train de télécharger. S'il vous plaît, attendez

Systèmes d'exploitation - SUPINFO Pierre Dimo - 2005Page 6.1 Chapitre 6 : Synchronisation des processus et des fils Introduction La section critique Matériel.

Présentations similaires


Présentation au sujet: "Systèmes d'exploitation - SUPINFO Pierre Dimo - 2005Page 6.1 Chapitre 6 : Synchronisation des processus et des fils Introduction La section critique Matériel."— Transcription de la présentation:

1 Systèmes d'exploitation - SUPINFO Pierre Dimo Page 6.1 Chapitre 6 : Synchronisation des processus et des fils Introduction La section critique Matériel spécifique Sémaphores Problèmes classiques de synchronisation Moniteurs

2 Systèmes d'exploitation - SUPINFO Pierre Dimo Page 6.2 Introduction Laccès concurrent aux données peut conduire a des résultats irrationnels Il faut prévoir des mécanismes pour lexécution ordonnée des processus qui coopèrent Les courses aux ressources communes. Macro-exemple : l'accès concomitant aux mêmes données dans plusieurs programmes.

3 Systèmes d'exploitation - SUPINFO Pierre Dimo Page 6.3 Gestion du solde d'un client Prise de commande Enregistrement de règlements Client C OK Mise à jour compte (4) S = S + C Mise à jour compte (3) S = S - R (1) (2) Validation du client (2) Enregistrer la commande Refus S

4 Systèmes d'exploitation - SUPINFO Pierre Dimo Page 6.4 Les courses Lutilisation de la mémoire partagée pour résoudre le problème du producteur / consommateur (ch.4) contient une possibilité de course sur la donnée count.

5 Systèmes d'exploitation - SUPINFO Pierre Dimo Page 6.5 Les courses Les appels producteurs while (true) { while (count == BUFFER_SIZE) sommeil() ; // ne rien faire, le tampon est plein // le tampon devient disponible : on produit produit_suivant buffer[in] = produit_suivant; in = (in + 1) % BUFFER_SIZE; count++; }

6 Systèmes d'exploitation - SUPINFO Pierre Dimo Page 6.6 Les courses (suite) Les appels consommateur while (true) { while (count == 0) sommeil(); // ne rien faire, le tampon est vide // le tampon contient quelque chose… consommé_suivant = buffer[out]; out = (out + 1) % BUFFER_SIZE; count--; // consomme lélément consommé_suivant }

7 Systèmes d'exploitation - SUPINFO Pierre Dimo Page 6.7 Les courses (suite) On peut programmer count++ ainsi registre1 = count registre1 = registre1 + 1 count = registre1 On peut programmer count-- ainsi registre2 = count registre2 = registre2 - 1 count = registre2 Soit maintenant la séquence suivante dexécution concomitante des deux fils,(count étant une variable en mémoire et R1, R2 deux registres différents) : S0: le producteur exécute registre1 <= count //registre1 = 5 S1: le producteur exécute registre1 <= register1 + 1 //registre1 = 6 S2: le consommateur exécute registre2 <= count //registre2 = 5 S3: le consommateur exécute registre2 <= registre2 - 1 //registre2 = 4 S4: le producteur exécute count <= registre1 //count = 6 S5: le consommateur exécute count <= registre2 //count = 4

8 Systèmes d'exploitation - SUPINFO Pierre Dimo Page 6.8 La section critique Une section critique du processus (ou fil) P est constituée par une séquence dinstructions qui modifient l'état de ressources communes (avec d'autres processus ou fils). Pour éliminer les courses, aucun processus concurrent ne doit exécuter une section critique pendant que P exécute les instructions d'une section critique (un processus concurrent est un processus qui fait appel a une ressource utilisée par P dans la section critique). Les critères qui régissent le fonctionnement correct de processus (ou fils) qui ont des sections critiques sont au nombre de 3 : – Exclusion mutuelle dans le temps – Le choix du processus qui exécutera la section critique parmi plusieurs demandeurs doit se faire seulement entre les processus concurrents (les autres processus ne participent pas au choix) – Continuité : tous les processus doivent exécuter les sections critiques (pour éviter que certains restent en état d'attente indéfinie)

9 Systèmes d'exploitation - SUPINFO Pierre Dimo Page 6.9 Solution pour le cas de 2 tâches Les deux tâches sappellent T 0 et T 1 Les instructions load, store et test sont atomiques On présente 3 solutions qui utilisent la même interface ExclusionMutuelle public interface ExclusionMutuelle { public static final int TOUR_0 = 0; public static final int TOUR_1 = 1; public abstract void solliciteSectionCritique(int tour); public abstract void quitteSectionCritique(int tour); } (interface désigne en Java des objets abstraits avec méthodes et champs qui doivent être matérialisés en classes à laide du mot clé « implement » )

10 Systèmes d'exploitation - SUPINFO Pierre Dimo Page 6.10 Le programme TestAlgorithmes Ce programme crée 2 fils utilisés pour tester les algorithmes : public class TestAlgorithmes { public static void main(String args[]) { ExclusionMutuelle alg = new Algorithm_1(); Thread first = new Thread( new usine("usine 0", 0, alg)); Thread second = new Thread(new usine("usine 1", 1, alg)); first.start(); second.start(); }

11 Systèmes d'exploitation - SUPINFO Pierre Dimo Page 6.11 Le fil usine public class usine implements Runnable { private String nom; private int id;// un nombre entier private ExclusionMutuelle mutex; public usine(String nom, int id, ExclusionMutuelle mutex) { this.nom = nom; this.id = id; this.mutex = mutex; } public void run() { while (true) { mutex.solliciteSectionCritique(id); Exemple.SectionCritique(nom); mutex.quitteSectionCritique(id); Exemple.SectionNormale(nom); }

12 Systèmes d'exploitation - SUPINFO Pierre Dimo Page 6.12 Algorithme 1 public class Algorithm_1 implements ExclusionMutuelle { private volatile int tour; public Algorithm 1() { tour = TOUR_0; } public void solliciteSectionCritique(int t) { while (tour != t)// ce nest pas mon tour…. Thread.yield();// laisse la place à un autre fil } public void quitteSectionCritique(int t) { tour = 1 - t;// si cetait le tour de 1 le suivant sera 0, et inversement}

13 Systèmes d'exploitation - SUPINFO Pierre Dimo Page 6.13 Algorithme_1 - explications Les fils se partagent la variable entière tour. Cependant, le partage de cette variable nest pas sans poser problème a cause des optimisations automatiques quun compilateur peut effectuer, comme par exemple lorsquil utilise le cache pour stocker une variable du programme qui ne change pas pendant plusieurs cycles dUC (tour dans la boucle). Cest la raison de lutilisation du qualificatif volatile Si tour == i, le fil i peut continuer (Exemple.SectionCritique...) Sinon, le fil sarrête et annonce (Thread.yield()) qu'il peut temporairement céder l'accès à l'UC pour permettre à lautre fil d'en prendre le contrôle. Problème de continuité : Lalternance des fils est stricte et obligatoire - seulement le fil pour lequel tour == i pourra entrer dans la section critique, même si lautre fil est dans une section non-critique. Ce qui peut conduire un fil à une attente infinie...

14 Systèmes d'exploitation - SUPINFO Pierre Dimo Page 6.14 Algorithme_2 On ajoute de linformation pour indiquer lintention du fil dentrer dans une section critique, à laide de 2 variables booléennes (fanions), initialisées à FAUX Le fil annonce son intention en changeant la valeur du fanion qui lui est associé en VRAI ; avant de pouvoir entrer dans sa section critique, lautre fil doit avoir positionné le fanion à FAUX (annoncer lintention de quitter la section critique). Le critère de la continuité nest toujours pas satisfait : si un changement de contexte intervient après le positionnement du fanion par un fil, mais avant que ce fil entre dans la boucle While, et que le deuxième fil (qui a provoqué le changement de contexte) positionne son fanion à VRAI, chacun des 2 fils attendra lautre…

15 Systèmes d'exploitation - SUPINFO Pierre Dimo Page 6.15 Algorithme_2 public class Algorithm_2 implements ExclusionMutuelle { private volatile boolean flag0, flag1; public Algorithm_2() { flag0 = false; flag1 = false; } public void solliciteSectionCritique(int t) { if (t == 0) {flag0 = true; while(flag1 == true) Thread.yield(); } else { flag1 = true; while (flag0 == true) Thread.yield();} public void quitteSectionCritique(int t) { if (t == 0) flag0 = false; else flag1 = false; }

16 Systèmes d'exploitation - SUPINFO Pierre Dimo Page 6.16 Algorithme_3 Combine les idées de 1 et 2 Satisfait-il aux critères de la section critique ?

17 Systèmes d'exploitation - SUPINFO Pierre Dimo Page 6.17 Algorithme_3 public class Algorithm_3 implements ExclusionMutuelle { private volatile boolean flag0; private volatile boolean flag1; private volatile int turn; public Algorithm_3() { flag0 = false; flag1 = false; turn = TURN_0; } // Suite…

18 Systèmes d'exploitation - SUPINFO Pierre Dimo Page 6.18 Algorithme_3 (suite) public void solliciteSectionCritique(int t) { int filno = 1 - t; turn = filno; if (t == 0) { flag0 = true; while(flag1 == true && turn == filno) Thread.yield(); } else { flag1 = true; while (flag0 == true && turn == filno) Thread.yield(); } // Suite

19 Systèmes d'exploitation - SUPINFO Pierre Dimo Page 6.19 Algorithme_3 (suite et fin) public void quitteSectionCritique(int t) { if (t == 0) flag0 = false; else flag1 = false; } }// fin

20 Systèmes d'exploitation - SUPINFO Pierre Dimo Page 6.20 Spécifications matérielles destinées à la synchronisation des processus Mono-processeurs – désactivation des interruptions – Le code en cours (section critique) peut sexécuter sans être interrompu – Sur les systèmes multi-processeurs la solution est inefficace, car la désactivation et la réactivation des interruptions doit être appliquée à tous les processeurs, ce qui peut être très coûteux en temps de traitement. Dans ces systèmes il est en plus très difficile de prendre en compte des chagements de configuration (ajout de processeurs) Utilisation des instructions atomiques (indivisibles et non-interruptibles) – Instructions TAS (Test-And-Set) – Instructions CAS (Compare-And-Swap) Ces instructions sont maintenant répandues dans les architectures multiprocesseurs car elle sont indipensables pour la réalisation de SE fiables. Les instructions atomiques du niveau matériel ne sont généralement pas à la portée des programmeurs dapplications.

21 Systèmes d'exploitation - SUPINFO Pierre Dimo Page 6.21 Simulation de léquipement (mémoire et instructions atomiques) public class HardwareData { private boolean data; public HardwareData(boolean data) { this.data = data; } // pour accéder a DATA public boolean get() { return data; } public void set(boolean data) { this.data = data; } //Suite…

22 Systèmes d'exploitation - SUPINFO Pierre Dimo Page 6.22 Simulation de léquipement (mémoire et instructions atomiques) suite public boolean getAndSet(boolean data) { boolean oldValue = this.get(); this.set(data); return oldValue; } public void swap(HardwareData other) { boolean temp = this.get(); this.set(other.get()); other.set(temp); } }// fin de la définition de la classe

23 Systèmes d'exploitation - SUPINFO Pierre Dimo Page 6.23 Fil utilisant une commande get-and-set // le verrou est partagé par tous les fils HardwareData lock = new HardwareData(false); while (true) { while (lock.getAndSet(true)) Thread.yield();// attend la libération du verrou sectioncritique(); lock.set(false); sectionNonCritique(); }

24 Systèmes d'exploitation - SUPINFO Pierre Dimo Page 6.24 Fil utilisant une instruction swap // le verrou est partagé par tous les fils HardwareData lock = new HardwareData(false); // chaque fil possède une copie locale de la clé (key) HardwareData key = new HardwareData(true); while (true) { // boucle infinie pour tester key.set(true); do { lock.swap(key); } while (key.get() == true); sectionCritique(); lock.set(false); sectionNonCritique(); }

25 Systèmes d'exploitation - SUPINFO Pierre Dimo Page 6.25 Les Sémaphores Cest une technique courante pour la synchronisation des processus au niveau des programmes utilisateurs Désavantage : le processus qui souhaite acquérir le sémaphore doit attendre si ce dernier est déjà pris par un autre processus : spinlock Un sémaphore cest une variable de type entier dont lutilisation se réduit à 3 commandes atomiques : – Initialisation – Acquisition (obtenir) obtenir(S) { while S <= 0; // on ne fait rien, un autre processus a acquis le //sémaphore S--;// on déduit 1 (sémaphore pris) } – Libération (liberer) liberer(S) { S++;// on ajoute 1 (sémaphore libéré) }

26 Systèmes d'exploitation - SUPINFO Pierre Dimo Page 6.26 Les Sémaphores comme outils de synchronisation Sémaphore compteur – une variable qui peut prendre nimporte quelle valeur entière Sémaphore binaire – une variable qui peut prendre seulement les valeurs 0 et 1 Pour réaliser lexclusion nécessaire en cas de sections critiques, Semaphore S; // initialisation à 1 obtenir(S); sectioncritique(); liberer(S);

27 Systèmes d'exploitation - SUPINFO Pierre Dimo Page 6.27 Le désavantage principal des algorithmes de synchronisation présentés antérieurement consiste dans le fait que lorsquun processus est dans la section critique, tout autre processus qui veut exécuter la section critique doit attendre en bouclant, ce qui constitue une utilisation improductive de lUC. Dautres processus pourraient lutiliser. Les verrous qui agissent ainsi portent le nom de verrous tournants (spinlocks). Ils sont utiles lorsque les attentes prévues sont courtes ou dans le cas des multiprocesseurs car un fil peut sexécuter sur un processeur différent. Les semaphores (suite)

28 Systèmes d'exploitation - SUPINFO Pierre Dimo Page 6.28 On peut éviter les verrous tournants en utilisant une queue dattente et le blocage du fil le temps de libérer le processeur occupé par la section critique dun autre processus. Le fil bloqué passe en état dattente et lordonnanceur donne le contrôle à un autre processus. Le processus bloquant exécute une opération de libération du sémaphore lorsque la section critique sest terminée et le processus bloqué est réveillé pour le passer dans la queue des processus prêts. Pour fonctionner ainsi, le sémaphore est construit avec une variable entière et une liste de processus. Les semaphores (suite)

29 Systèmes d'exploitation - SUPINFO Pierre Dimo Page 6.29 Synchronisation à laide de sémaphore - usine public class usine implements Runnable{ private Semaphore sem; private String nom; public usine(Semaphore sem, String nom) { this.sem = sem; this.nom = nom; } public void run() { while (true) { sem.obtenir(); // en attente du sémaphore MutlExUtil.sectioncritique(nom); sem.liberer(); MutlExUtil.sectionNonCritique(nom); }

30 Systèmes d'exploitation - SUPINFO Pierre Dimo Page 6.30 Synchronisation à laide de sémaphore – le programme principal public class SimulateurSemaphore { public static void main(String args[]) { Semaphore sem = new Semaphore(1); Thread[ ] fils = new Thread[5]; for (int i = 0; i < 5; i++) fils[i] = new Thread(new usine(sem, "usine " + (new Integer(i)).toString() )); for (int i = 0; i < 5; i++) fils[i].start(); }

31 Systèmes d'exploitation - SUPINFO Pierre Dimo Page 6.31 Implémentation du sémaphore obtenir(S){ value--; if (value < 0) { ajouter ce processus à la liste dattente bloquer ; // au lieu de boucler, ce qui permet à lOrdonnanceur den choisir un autre pour exécution. } liberer(S){ value++; if (value <= 0) { enlever un processus de la liste dattente réveiller(P); }

32 Systèmes d'exploitation - SUPINFO Pierre Dimo Page 6.32 Implémentation du sémaphore Les opérations de blocage, mise en queue dattente, réveil et remise dans la queue des processus prêts sont implémentées comme des primitives du SE. Doit garantir limpossibilité que deux processus soient capables dexécuter obtenir() et liberer() sur le même sémaphore en même temps Limplémentation est ainsi réduite à un problème de section critique, avec les conséquences décrites antérieurement (risques dattentes prolongées mais facilité de réalisation)

33 Systèmes d'exploitation - SUPINFO Pierre Dimo Page 6.33 Blocages (Deadlock and Starvation) Lutilisation des sémaphores et des queues dattente peut donner lieu à 2 situations de blocage : Interblocage : deux ou plusieurs processus attendent indéfiniment un évènement quun autre parmi eux peut seul produire (etreinte mortelle). – Exemple : Soit S et Q deux sémaphores initialisés à 1 P 0 P 1 obtenir(S); obtenir(Q); obtenir(Q); obtenir(S);. liberer(S); liberer(Q); liberer(Q); liberer(S); – Attente infinie : le processus nest jamais enlevé de la queue daccès au sémaphore

34 Systèmes d'exploitation - SUPINFO Pierre Dimo Page 6.34 Problèmes classiques de synchronisation Le tampon de taille finie (problème du producteur / consommateur) Lecture et écriture dans une base de données Le problème des philosophes

35 Systèmes d'exploitation - SUPINFO Pierre Dimo Page 6.35 Le tampon de taille finie public class BoundedBuffer implements Buffer { private static final int BUFFER SIZE = 5; // 5 objets produits/consommés private Object[] buffer; private int in, out; private Semaphore mutex; // assure lexclusion mutuelle lors de laccès au tampon private Semaphore empty; // compteur du nombre de tampons libres private Semaphore full; // compteur du nombre de tampons occupés // Suite

36 Systèmes d'exploitation - SUPINFO Pierre Dimo Page 6.36 Le tampon de taille finie (suite) public BoundedBuffer() { // le tampon est initialement vide in = 0; out = 0; buffer = new Object[BUFFER SIZE]; mutex = new Semaphore(1); empty = new Semaphore(BUFFER SIZE); full = new Semaphore(0); } public void insert(Object item) { /* voir suite */ } public Object remove() { /* voir suite */ } }

37 Systèmes d'exploitation - SUPINFO Pierre Dimo Page 6.37 Le tampon de taille finie (suite) public void insert(Object item) { empty.obtenir(); mutex.obtenir(); // ajouter un élément au tampon buffer[in] = item; in = (in + 1) % BUFFER SIZE; mutex.liberer(); full.liberer(); }

38 Systèmes d'exploitation - SUPINFO Pierre Dimo Page 6.38 Le tampon de taille finie (suite) public Object remove() { full.obtenir(); mutex.obtenir(); // enlève un élément du tampon Object item = buffer[out]; out = (out + 1) % BUFFER SIZE; mutex.liberer(); empty.liberer(); return item; }

39 Systèmes d'exploitation - SUPINFO Pierre Dimo Page 6.39 Le tampon de taille finie (suite) import java.util.Date; public class Producer implements Runnable {// le producteur private Buffer buffer; public Producer(Buffer buffer) { this.buffer = buffer; } public void run() { Date message; while (true) { // attendre… SleepUtilities.nap(); // produit un message et ajoute au tampon message = new Date(); buffer.insert(message); }

40 Systèmes d'exploitation - SUPINFO Pierre Dimo Page 6.40 Le tampon de taille finie (suite) import java.util.Date; public class Consumer implements Runnable {// le consommateur private Buffer buffer; public Consumer(Buffer buffer) { this.buffer = buffer; } public void run() { Date message; while (true) { // attendre… SleepUtilities.nap(); // enlève un élément du tampon message = (Date)buffer.remove(); }

41 Systèmes d'exploitation - SUPINFO Pierre Dimo Page 6.41 Le tampon de taille finie (suite) public class Factory { public static void main(String args[]) { Buffer buffer = new BoundedBuffer(); // création des fils consommateur et producteur Thread producer = new Thread(new Producer(buffer)); Thread consumer = new Thread(new Consumer(buffer)); producer.start(); consumer.start(); }

42 Systèmes d'exploitation - SUPINFO Pierre Dimo Page 6.42 class Buffer { private static final MAX_AVAILABLE = 100; private final Semaphore available = new Semaphore(MAX_AVAILABLE, true); public Object getItem() throws InterruptedException { available.acquire(); // si disponibilité obtient le sémaphore, sinon bloque le thread return getNextAvailableItem(); } public void putItem(Object x) { if (markAsUnused(x)) available.release(); // libère le sémaphore } protected Object[] items =... Les objets quil faut gérer de manière protégée boolean[] used = new boolean[MAX_AVAILABLE]; protected synchronized Object getNextAvailableItem() { for (int i = 0; i < MAX_AVAILABLE; ++i) { if (!used[i]) { used[i] = true; return items[i]; } return null; // not reached } protected synchronized boolean markAsUnused(Object item) { for (int i = 0; i < MAX_AVAILABLE; ++i) { if (item == items[i]) { if (used[i]) { used[i] = false; return true; } else return false; } } return false; }

43 Systèmes d'exploitation - SUPINFO Pierre Dimo Page 6.43 Lecture et écriture dans une base de données public class Reader implements Runnable {// la fonction lecture private RWLock db; public Reader(RWLock db) { this.db = db; } public void run() { while (true) { // attendre db.obtenirReadLock(); // Accès accordé // Lecture db.libererReadLock(); }

44 Systèmes d'exploitation - SUPINFO Pierre Dimo Page 6.44 Lecture et écriture dans une BD (suite) public class Writer implements Runnable {// la fonction écriture { private RWLock db; public Writer(RWLock db) { this.db = db; } public void run() { while (true) { db.obtenirWriteLock(); // Accès accordé // Ecrit db.libererWriteLock(); }

45 Systèmes d'exploitation - SUPINFO Pierre Dimo Page 6.45 Lecture et écriture dans une BD (suite) public interface RWLock { public abstract void obtenirReadLock(); public abstract void obtenirWriteLock(); public abstract void libererReadLock(); public abstract void libererWriteLock(); }

46 Systèmes d'exploitation - SUPINFO Pierre Dimo Page 6.46 Lecture et écriture dans une BD (suite) La base de données public class Database implements RWLock { private int readerCount; private Semaphore mutex; private Semaphore db; public Database() { readerCount = 0; mutex = new Semaphore(1); db = new Semaphore(1); } public int obtenirReadLock() { /* voir suite */ } public int libererReadLock() {/* voir suite */ } public void obtenirWriteLock() {/* voir suite */ } public void libererWriteLock() {/* voir suite */ } }

47 Systèmes d'exploitation - SUPINFO Pierre Dimo Page 6.47 Lecture et écriture dans une BD (suite) Méthodes utilisée pour la lecture public void obtenirReadLock() { mutex.obtenir(); ++readerCount; // le premier lecteur en cours de lecture if (readerCount == 1) db.obtenir(); mutex.liberer(); } public void libererReadLock() { mutex.obtenir(); --readerCount; // le dernier lecteur a terminé if (readerCount == 0) db.liberer(); mutex.liberer(); }

48 Systèmes d'exploitation - SUPINFO Pierre Dimo Page 6.48 Lecture et écriture dans une BD (suite) Méthodes utilisée pour lécriture public void obtenirWriteLock() { db.obtenir(); } public void libererWriteLock() { db.liberer(); }

49 Systèmes d'exploitation - SUPINFO Pierre Dimo Page 6.49 Le problème des philosophes chinois

50 Systèmes d'exploitation - SUPINFO Pierre Dimo Page 6.50 Le problème des philosophes chinois - enoncé Énoncé – 5 philosophes réfléchissent autour dune table ronde – Chacun a devant lui un plat de riz – Entre chaque plat de riz est disposée une baguette – Pour manger, un philosophe doit utiliser 2 baguettes, mais ne peut utiliser que celles qui se trouvent autour de son plat Baguettes = données partagées (stick = baguette). Le philosophe qui veut manger prend les baguettes dans lordre gauche -> droite Seulement 2 philosophes peuvent manger en même temps Les deux philosophes qui mangent ne peuvent pas se trouver lun a coté de lautre – Après avoir mangé, un philosophe pose les baguettes utilisées au même endroit et dans le même ordre. Ce problème est représentatif dune large classe de problèmes de synchronisation de processus

51 Systèmes d'exploitation - SUPINFO Pierre Dimo Page 6.51 Le problème des philosophes chinois (suite) 1.On numérote les baguettes et les philosophes de 0 à 4. La baguette de gauche porte le même numéro que le philosophe, celle de droite, le numéro du philosophe Chaque baguette joue le rôle dun sémaphore 3.Les philosophes essayent de prendre systématiquement les baguettes de leur gauche en premier. 4.Si le philosophe a réussi a obtenir la baguette de gauche il essaye de récupérer celle de droite. 5.Lorsque la baguette demandée est prise par un autre philosophe, celui qui la demande attend.

52 Systèmes d'exploitation - SUPINFO Pierre Dimo Page 6.52 Le problème des philosophes chinois (suite) On peut essayer de traiter le problème en représentant chaque baguette (stick) par un sémaphore : Semaphore baguette[] = new semaphore[5] Philosophe i: while (true) { // prend la baguette de gauche baguette[i].obtenir(); // prend la baguette de droite baguette[(i + 1) % 5].obtenir(); manger(); // pose la baguette de gauche baguette[i].liberer(); // pose la baguette de droite baguette[(i + 1) % 5].liberer(); reflechir();// Réfléchit… }

53 Systèmes d'exploitation - SUPINFO Pierre Dimo Page 6.53 Le problème des philosophes chinois (suite) La solution ne satisfait pas car elle peut conduire à un blocage : cest le cas ou tous les philosophes décident en même temps de manger Pour contourner le problème on peut : – Obliger au maximum 4 philosophes de décider simultanément de manger – Permettre à un philosophe de manger seulement si les deux baguettes sont à sa portée, mais dans ce cas lopération doit être réalisée de manière « atomique » (section critique). – Utiliser une règle dassymetrie selon laquelle : les philosophes « impairs » commencent par prendre la baguette de droite et les philosophes « pairs » commencent par prendre la baguette de gauche La solution ne doit pas seulement éviter le blocage, il faut également faire en sorte quaucun philosophe ne meurt de faim… parce quil na jamais accès aux baguettes !

54 Systèmes d'exploitation - SUPINFO Pierre Dimo Page 6.54 Problème des sémaphores Des erreurs de programmation peuvent se produire dans la logique dacquisition ou dans le « timing » des acquisitions / libérations des sémaphores. Si il y a des erreurs, elle sont difficilement détectables car elles se manifestent de manière « aléatoire » (du point de vue de lutilisateur), c.a.d. seulement lorsque sont exécutées certaines séquences qui nont pas un caractère régulier

55 Systèmes d'exploitation - SUPINFO Pierre Dimo Page 6.55 Problème des sémaphores (suite) Exemples derreurs – Inversion de lordre des accès aux sémaphores : plusieurs processus peuvent exécuter leur section critique en même temps… smphr.liberer() SectionCritique() smphr.obtenir() – Demande double du même sémaphore : blocage smphr.obtenir() sectioncritique() smphr.obtenir() – Oubli dune des demandes (obtenir ou liberer) : tout peut arriver !

56 Systèmes d'exploitation - SUPINFO Pierre Dimo Page 6.56 Les moniteurs : une approche différente de la synchronisation Le moniteur est un module qui réalise une abstraction sécurisée de lexécution des fils Dans le moniteur, un seul fil peut être actif à la fois, lexclusion réciproque des fils est assurée par le moniteur Les structures internes du moniteur ne sont pas visibles de lextérieur et aucun fil ne peut les adresser directement. Des varibles du type CONDITION permettent au programmeur de faire appel aux méthodes WAIT et SIGNAL du moniteur sous la forme – X.Wait – X.Signal Lopération Wait met en attente le fil dans une queue si la condition X est remplie jusquau moment ou un autre fil envoie le message Signal. Si aucun fil nest suspendu lorsque Signal arrive, il ne se passe rien. Pour assurer un fonctionnement sans erreurs, lorsque un processus P envoie le message Signal il quitte le Moniteur et un processus Q en attente le remplace immédiatement.

57 Systèmes d'exploitation - SUPINFO Pierre Dimo Page 6.57 Moniteur avec variables de condition Queue des demandes daccès au moniteur Données partagées Code moniteur Opérations

58 Systèmes d'exploitation - SUPINFO Pierre Dimo Page 6.58 Les philosophes : solution avec moniteur. monitor Philosophes { int[] etat = new int[5]; static final int REFLECHIT = 0; static final int FAIM = 1; static final int MANGE = 2; condition[] self = new condition[5]; // instantiation de la classe ; tous les philosophes sont en train de réfléchir public Philosophes { for (int i = 0; i < 5; i++) state[i] = THINKING; } // Suite

59 Systèmes d'exploitation - SUPINFO Pierre Dimo Page 6.59 Les Philosophes – solution avec MONITEUR (suite) Le rôle des variables : int[] etat = new int[5];// décrit létat de chaque Philosophe static final int REFLECHIT = 0;// état en cours de réflexion static final int FAIM = 1;// état a faim et veut manger static final int MANGE = 2;// état en train de manger condition[] self = new condition[5];// permet denregistrer létat dattente lorsque le processus ne peut pas continuer Notre solution impose que la personne qui mange soit entourée de personne qui ne sont pas en train de manger, c.a.d. quest remplie la condition : (etat[i+4]%5 != MANGE) && (etat[i+5]/%5 != MANGE)

60 Systèmes d'exploitation - SUPINFO Pierre Dimo Page 6.60 Les philosophes : solution avec moniteur (suite). public levebag(int i) { etat[i] = FAIM; test(i); if (etat[i] != MANGE) self[i].wait; } public posebag(int i) { etat[i] = REFLECHIT; } private test(int i) { if ( (etat[(i + 4) % 5] != MANGE) && (etat[i] == FAIM) && (state[(i + 1) % 5] != MANGE) ) { setat[i] = MANGE; self[i].signal; }

61 Systèmes d'exploitation - SUPINFO Pierre Dimo Page 6.61 Les philosophes : solution avec moniteur (suite). Avant de manger, un philosophe doit essayer de lever les baguettes (levebag). Cette méthode fait appel à le méthode test qui vérifie que les deux baguettes sont disponibles. Si la demande réussit, létat du philosophe passe à MANGE (etat[i]=MANGE dans test), sinon le fil est mis en attente (self[i].wait dans levebag) Les opérations doivent se dérouler dans lordre suivant : philor.levebag(i); mange() ;// opération se déroulant sans risque philo.posebag(i); ou mntr est une instance de la classe PHILOSOPHES

62 Systèmes d'exploitation - SUPINFO Pierre Dimo Page 6.62 Méthodes spécifiques Méthodes spécifiques : synchronized, wait(), notify() Notifications multiples Synchronization des blocs Les Semaphores Java Les moniteurs Java

63 Systèmes d'exploitation - SUPINFO Pierre Dimo Page 6.63 Le qualificatif synchronized Java associe à chaque objet un verrou. Lors de linvocation des méthodes de lobjet le verrou est ignoré. Le verrou est activé seulement si la méthode a été déclarée « synchronized » Pour appeler une méthode synchronisée il faut obtenir le verrou. Si le verrou est possédé par un autre thread, celui qui appelle la méthode sera mis en attente, dans la queue des threads qui demandent laccès à la méthode verrouillée (entry set). Le verrou est libéré lorsque le thread quitte la méthode synchronisée Pour la sélection du thread qui prendra le contrôle du verrou, la machine virtuelle Java (JVM) applique normalement un algorithme FIFO

64 Systèmes d'exploitation - SUPINFO Pierre Dimo Page 6.64 Exemple « producteur / consommateur » Public synchronized void insert(Object o) { while (cmpt == TAILLE_TAMPON) thread.yield(); cmpt++; tampon[i] = o; i = (i + 1)%TAILLE_TAMPON; } Public synchronized Object enleve(){ Object o; while (cmpt == 0) thread.yield(); --cmpt; o=tampon[k]; k = (k+1)%TAILLE_TAMPON; return o; } Ces méthodes sont appliquées au tampon commun du producteur et du consommateur. Elles nous assurent quil ny aura pas de « course » entre les deux pour modifier cmpt.

65 Systèmes d'exploitation - SUPINFO Pierre Dimo Page 6.65 Problème posé par synchronized La solution exposée peut conduire à une situation de blocage réciproque : – Le consommateur « dort » – Le tampon est plein – Le producteur produit et se met en attente de consommation, mais a trouvé le verrou disponible et en a pris le contrôle… – Le consommateur se « reveille » mais ne peut pas accéder à lobjet, car il est verrouillé !

66 Systèmes d'exploitation - SUPINFO Pierre Dimo Page 6.66 Solution : les méthodes wait() et notify() Chaque objet Java possède en plus du verrou, un ensemble appelé wait set, destiné à contenir des instances en attente dexécution. Si le thread entre dans une méthode synchronisée quil ne peut pas exécuter jusquà sa fin (cas du producteur qui se trouve devant un tampon plein), il – va libérer le verrou acquis (en entrant dans la méthodes synchronisée), – sera mis en état « arrêté » – sera placé dans lensemble dattente (le wait set), le temps nécessaire pour que la condition qui la empêché de continuer soit enfin réalisée Le thread qui peut débloquer la situation (dans notre exemple, le consommateur), doit annoncer que le thread bloqué peut continuer. On utilise à cet effet la méthode notify() qui : – Extrait (au « hasard ») un thread de la liste dattente – Déplace le thread dans la liste des processus demandeurs daccès au verrou (entry set) – Change létat du thread, de bloqué en exécutable

67 Systèmes d'exploitation - SUPINFO Pierre Dimo Page 6.67 Producteur / conommateur : gestion du verrou Public synchronized void insert(Object o){ while (cmpt == TAILLE_TAMPON){// le tampon est-il plein ? try{ wait();// oui : enlève le verrou et déplace le thread -> queue dattente } catch (InterruptedException e){ } } cmpt++;// le thread peut continuer… tampon[i] = o; i = (i+1)%TAILLE_TAMPON; notify();// annonce la fin de la méthode synchronisée }

68 Systèmes d'exploitation - SUPINFO Pierre Dimo Page 6.68 Producteur / consommateur : gestion du verrou Public synchronized Object enleve(){ Object o; while (cmpt == 0){// le tampon est-il vide ? try { wait();// oui : enlève le verrou et déplace le thread -> queue dattente } catch(InterruptedException e) { } } --cmpt;// le thread peut continuer… o=tampon[k]; k = (k+1)%TAILLE_TAMPON; notify();// annonce la sortie de la méthode synchronisée return o; }

69 Systèmes d'exploitation - SUPINFO Pierre Dimo Page 6.69 notifyAll() Plusieurs threads peuvent se trouver dans la queue dattente pour des raisons différentes. Il devient difficile de gérer leur réactivation avec notify(). La méthode notifyAll() permet de débloquer tous les threads de la queue dattente pour les déplacer dans la queue dentrée. La méthode notifyAll() réduit les performances mais est plus sécurisante.

70 Systèmes d'exploitation - SUPINFO Pierre Dimo Page 6.70 Synchronisation des blocs Les procédures synchronisées peuvent être pénalisantes si elles sont complexes. Java donne la possibilité de synchroniser seulement les parties « sensibles » du code, en utilisant un objet que lon peut appelé Verrou, comme dans lexemple ci-dessous : Object Verrou = new Object(); ………… Public void uneMéthode() { sectionNonCritique() ; synchronized(Verrou) { sectionCritique() } sectionNonCritique }

71 Systèmes d'exploitation - SUPINFO Pierre Dimo Page 6.71 Gestion des interruptions Un thread peut être interrompu par un autre thread en utilisant la méthode interrupt() ce qui positionne un fanion dans le thread interrompu, mais ne larrête pas : larrêt effectif est de la responsabilité du thread interrompu, qui doit donc tester le fanion (isInterrupted()) La méthode wait() teste également ce fanion. Si il est « vrai », une exception est générée (le fanion est en même temps remis à « faux »). Lutilisation de wait() nous a obligé de prévoir la paire de blocs try{} / catch{} qui permettent de gérer les méthodes dans lesquelles il peut se produire des interruptions. Les interruptions ainsi générées peuvent être traitées dans le thread, mais cela na pas été fait dans les exemples précédents pour des raisons de clarté.

72 Systèmes d'exploitation - SUPINFO Pierre Dimo Page 6.72 Quelques règles concernant la synchronisation des processus Un thread qui contrôle un verou dun objet, peut entrer dans une autre méthode synchronisée et prendre le contrôle dun autre verrou du même objet : verrouillage récursif. Le même thread peut contrôler des verrous appartenant à des objets différents Une méthode non-synchronisée peut être appelée quelle que soit létat de lobjet auquel elle est rattachée : elle sera utilisable même si un verrou a été pris par un thread sur le même objet (c.a.d. quun autre thread exécute une méthode synchronisée). Si la queue dattente (le wait set) dun objet est vide, les méthodes notify() et notifyall() sont sans effet. Les méthodes wait(), notify() et notifyAll() peuvent être invoquées seulement à partir de méthodes synchronisées..


Télécharger ppt "Systèmes d'exploitation - SUPINFO Pierre Dimo - 2005Page 6.1 Chapitre 6 : Synchronisation des processus et des fils Introduction La section critique Matériel."

Présentations similaires


Annonces Google