Chapitre 6 Synchronisation des processus et des fils

  • Slides: 72
Download presentation
Chapitre 6 : Synchronisation des processus et des fils ● ● ● Introduction La

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

Introduction ● ● L’accès concurrent aux données peut conduire a des résultats irrationnels Il

Introduction ● ● L’accès concurrent aux données peut conduire a des résultats irrationnels Il faut prévoir des mécanismes pour l’exé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. Systèmes d'exploitation - SUPINFO Page 6. 2 Pierre Dimo - 2005

Gestion du solde d'un client Prise de commande (1) Refus Client C Enregistrement de

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

Les courses ● L’utilisation de la mémoire partagée pour résoudre le problème du producteur

Les courses ● L’utilisation 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. Systèmes d'exploitation - SUPINFO Page 6. 4 Pierre Dimo - 2005

Les courses Les appels producteurs while (true) { while (count == BUFFER_SIZE) sommeil() ;

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_suivant buffer[in] = produit_suivant; in = (in + 1) % BUFFER_SIZE; count++; } Systèmes d'exploitation - SUPINFO Page 6. 5 Pierre Dimo - 2005

Les courses (suite) Les appels consommateur while (true) { while (count == 0) sommeil()

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 } Systèmes d'exploitation - SUPINFO Page 6. 6 Pierre Dimo - 2005

Les courses (suite) ● On peut programmer count++ ainsi registre 1 = count registre

Les courses (suite) ● On peut programmer count++ ainsi registre 1 = count registre 1 = registre 1 + 1 count = registre 1 ● On peut programmer count-- ainsi registre 2 = count registre 2 = registre 2 - 1 count = registre 2 ● Soit maintenant la séquence suivante d’exécution concomitante des deux fils , (count étant une variable en mémoire et R 1, R 2 deux registres différents) : S 0: le producteur exécute registre 1 <= count //registre 1 = 5 S 1: le producteur exécute registre 1 <= register 1 + 1 //registre 1 = 6 S 2: le consommateur exécute registre 2 <= count //registre 2 = 5 S 3: le consommateur exécute registre 2 <= registre 2 - 1 //registre 2 = 4 S 4: le producteur exécute count <= registre 1 //count = 6 S 5: le consommateur exécute count <= registre 2 //count = 4 Systèmes d'exploitation - SUPINFO Page 6. 7 Pierre Dimo - 2005

La section critique ● ● ● Une section critique du processus (ou fil) P

La section critique ● ● ● Une section critique du processus (ou fil) P est constituée par une séquence d’instructions 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) Systèmes d'exploitation - SUPINFO Page 6. 8 Pierre Dimo - 2005

Solution pour le cas de 2 tâches Les deux tâches s’appellent T 0 et

Solution pour le cas de 2 tâches Les deux tâches s’appellent T 0 et T 1 ● Les instructions load, store et test sont “atomiques” ● On présente 3 solutions qui utilisent la même interface Exclusion. Mutuelle ● public interface Exclusion. Mutuelle { public static final int TOUR_0 = 0; public static final int TOUR_1 = 1; public abstract void sollicite. Section. Critique(int tour); public abstract void quitte. Section. Critique(int tour); } (interface désigne en Java des objets abstraits avec méthodes et champs qui doivent être matérialisés en classes à l’aide du mot cléPage « implement » ) 6. 9 Pierre Dimo - 2005 Systèmes d'exploitation - SUPINFO

Le programme Test. Algorithmes Ce programme crée 2 fils utilisés pour tester les algorithmes

Le programme Test. Algorithmes Ce programme crée 2 fils utilisés pour tester les algorithmes : public class Test. Algorithmes { public static void main(String args[]) { Exclusion. Mutuelle 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(); } } Systèmes d'exploitation - SUPINFO Page 6. 10 Pierre Dimo - 2005

Le fil usine public class usine implements Runnable { private String nom; private int

Le fil usine public class usine implements Runnable { private String nom; private int id; // un nombre entier private Exclusion. Mutuelle mutex; public usine(String nom, int id, Exclusion. Mutuelle mutex) { this. nom = nom; this. id = id; this. mutex = mutex; } public void run() { while (true) { mutex. sollicite. Section. Critique(id); Exemple. Section. Critique(nom); mutex. quitte. Section. Critique(id); Exemple. Section. Normale(nom); } } } Systèmes d'exploitation - SUPINFO Page 6. 11 Pierre Dimo - 2005

Algorithme 1 public class Algorithm_1 implements Exclusion. Mutuelle { private volatile int tour; public

Algorithme 1 public class Algorithm_1 implements Exclusion. Mutuelle { private volatile int tour; public Algorithm 1() { tour = TOUR_0; } public void sollicite. Section. Critique(int t) { while (tour != t) // ce n’est pas mon tour…. Thread. yield(); // laisse la place à un autre fil } public void quitte. Section. Critique(int t) { tour = 1 - t; // si c’etait le tour de 1 le suivant sera 0, et inversement } } Systèmes d'exploitation - SUPINFO Page 6. 12 Pierre Dimo - 2005

Algorithme_1 - explications ● ● Les fils se partagent la variable entière tour. Cependant,

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

Algorithme_2 ● ● On ajoute de l’information pour indiquer l’intention du fil d’entrer dans

Algorithme_2 ● ● On ajoute de l’information pour indiquer l’intention du fil d’entrer dans une section critique, à l’aide 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, l’autre fil doit avoir positionné le fanion à FAUX (annoncer l’intention de quitter la section critique). Le critère de la continuité n’est 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 Page 6. 14 Pierre Dimo - 2005 Systèmes d'exploitation - SUPINFO ●

Algorithme_2 public class Algorithm_2 implements Exclusion. Mutuelle { private volatile boolean flag 0, flag

Algorithme_2 public class Algorithm_2 implements Exclusion. Mutuelle { private volatile boolean flag 0, flag 1; public Algorithm_2() { flag 0 = false; flag 1 = false; } public void sollicite. Section. Critique(int t) { if (t == 0) { flag 0 = true; while(flag 1 == true) Thread. yield(); } else { flag 1 = true; while (flag 0 == true) Thread. yield(); } } public void quitte. Section. Critique(int t) { if (t == 0) flag 0 = false; else flag 1 = false; } } Systèmes d'exploitation - SUPINFO Page 6. 15 Pierre Dimo - 2005

Algorithme_3 ● ● Combine les idées de 1 et 2 Satisfait-il aux critères de

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

Algorithme_3 public class Algorithm_3 implements Exclusion. Mutuelle { private volatile boolean flag 0; private

Algorithme_3 public class Algorithm_3 implements Exclusion. Mutuelle { private volatile boolean flag 0; private volatile boolean flag 1; private volatile int turn; public Algorithm_3() { flag 0 = false; flag 1 = false; turn = TURN_0; } // Suite… Systèmes d'exploitation - SUPINFO Page 6. 17 Pierre Dimo - 2005

Algorithme_3 (suite) public void sollicite. Section. Critique(int t) { int filno = 1 -

Algorithme_3 (suite) public void sollicite. Section. Critique(int t) { int filno = 1 - t; turn = filno; if (t == 0) { flag 0 = true; while(flag 1 == true && turn == filno) Thread. yield(); } else { flag 1 = true; while (flag 0 == true && turn == filno) Thread. yield(); } } // Suite Systèmes d'exploitation - SUPINFO Page 6. 18 Pierre Dimo - 2005

Algorithme_3 (suite et fin) public void quitte. Section. Critique(int t) { if (t ==

Algorithme_3 (suite et fin) public void quitte. Section. Critique(int t) { if (t == 0) flag 0 = false; else flag 1 = false; } } // fin Systèmes d'exploitation - SUPINFO Page 6. 19 Pierre Dimo - 2005

Spécifications matérielles destinées à la synchronisation des processus ● Mono-processeurs – désactivation des interruptions

Spécifications matérielles destinées à la synchronisation des processus ● Mono-processeurs – désactivation des interruptions – – ● Utilisation des instructions “atomiques” (indivisibles et non-interruptibles) – – ● Le code en cours (section critique) peut s’exé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) 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 d’applications. Systèmes d'exploitation - SUPINFO Page 6. 20 Pierre Dimo - 2005

Simulation de l’équipement (mémoire et instructions atomiques) public class Hardware. Data { private boolean

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

Simulation de l’équipement (mémoire et instructions atomiques) suite public boolean get. And. Set(boolean data)

Simulation de l’équipement (mémoire et instructions atomiques) suite public boolean get. And. Set(boolean data) { boolean old. Value = this. get(); this. set(data); return old. Value; } public void swap(Hardware. Data other) { boolean temp = this. get(); this. set(other. get()); other. set(temp); } } // fin de la définition de la classe Systèmes d'exploitation - SUPINFO Page 6. 22 Pierre Dimo - 2005

Fil utilisant une commande “get-and-set” // le verrou est partagé par tous les fils

Fil utilisant une commande “get-and-set” // le verrou est partagé par tous les fils Hardware. Data lock = new Hardware. Data(false); while (true) { while (lock. get. And. Set(true)) Thread. yield(); // attend la libération du verrou sectioncritique(); lock. set(false); section. Non. Critique(); } Systèmes d'exploitation - SUPINFO Page 6. 23 Pierre Dimo - 2005

Fil utilisant une instruction “swap” // le verrou est partagé par tous les fils

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

Les Sémaphores ● ● ● C’est une technique courante pour la synchronisation des processus

Les Sémaphores ● ● ● C’est 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 c’est une variable de type entier dont l’utilisation 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é) Page 6. 25 Systèmes }d'exploitation - SUPINFO Pierre Dimo - 2005

Les Sémaphores comme outils de synchronisation ● ● ● Sémaphore compteur – une variable

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

Les semaphores (suite) ● ● Le désavantage principal des algorithmes de synchronisation présentés antérieurement

Les semaphores (suite) ● ● Le désavantage principal des algorithmes de synchronisation présentés antérieurement consiste dans le fait que lorsqu’un 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 l’UC. D’autres processus pourraient l’utiliser. 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 s’exécuter sur un processeur différent. Systèmes d'exploitation - SUPINFO Page 6. 27 Pierre Dimo - 2005

Les semaphores (suite) ● ● ● On peut éviter les verrous tournants en utilisant

Les semaphores (suite) ● ● ● On peut éviter les verrous tournants en utilisant une queue d’attente et le blocage du fil le temps de libérer le processeur occupé par la section critique d’un autre processus. Le fil bloqué passe en état d’attente et l’ordonnanceur 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 s’est 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. Systèmes d'exploitation - SUPINFO Page 6. 28 Pierre Dimo - 2005

Synchronisation à l’aide de sémaphore - usine public class usine implements Runnable{ private Semaphore

Synchronisation à l’aide 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 Mutl. Ex. Util. sectioncritique(nom); sem. liberer(); Mutl. Ex. Util. section. Non. Critique(nom); } } Page 6. 29 Pierre Dimo - 2005 Systèmes d'exploitation - SUPINFO

Synchronisation à l’aide de sémaphore – le programme principal public class Simulateur. Semaphore {

Synchronisation à l’aide de sémaphore – le programme principal public class Simulateur. Semaphore { 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)). to. String() )); for (int i = 0; i < 5; i++) fils[i]. start(); } } Systèmes d'exploitation - SUPINFO Page 6. 30 Pierre Dimo - 2005

Implémentation du sémaphore obtenir(S){ value--; if (value < 0) { ajouter ce processus à

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

Implémentation du sémaphore Les opérations de blocage, mise en queue d’attente, réveil et remise

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

Blocages (Deadlock and Starvation) ● L’utilisation des sémaphores et des queues d’attente peut donner

Blocages (Deadlock and Starvation) ● L’utilisation des sémaphores et des queues d’attente peut donner lieu à 2 situations de blocage : ● . – Interblocage : deux ou plusieurs processus attendent indéfiniment un évènement qu’un 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(S); . liberer(S); liberer(Q); liberer(S); Attente infinie : le processus n’est jamais enlevé de la queue d’accès au sémaphore Systèmes d'exploitation - SUPINFO Page 6. 33 Pierre Dimo - 2005

Problèmes classiques de synchronisation ● ● ● Le tampon de taille finie (problème du

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 Systèmes d'exploitation - SUPINFO Page 6. 34 Pierre Dimo - 2005

Le tampon de taille finie public class Bounded. Buffer implements Buffer { private static

Le tampon de taille finie public class Bounded. Buffer 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 l’exclusion mutuelle lors de l’accès au tampon private Semaphore empty; // compteur du nombre de tampons libres private Semaphore full; // compteur du nombre de tampons occupés // Suite Systèmes d'exploitation - SUPINFO Page 6. 35 Pierre Dimo - 2005

Le tampon de taille finie (suite) public Bounded. Buffer() { // le tampon est

Le tampon de taille finie (suite) public Bounded. Buffer() { // 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 */ } } Systèmes d'exploitation - SUPINFO Page 6. 36 Pierre Dimo - 2005

Le tampon de taille finie (suite) public void insert(Object item) { empty. obtenir(); mutex.

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(); } Systèmes d'exploitation - SUPINFO Page 6. 37 Pierre Dimo - 2005

Le tampon de taille finie (suite) public Object remove() { full. obtenir(); mutex. obtenir();

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; } Systèmes d'exploitation - SUPINFO Page 6. 38 Pierre Dimo - 2005

Le tampon de taille finie (suite) import java. util. Date; public class Producer implements

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

Le tampon de taille finie (suite) import java. util. Date; public class Consumer implements

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… Sleep. Utilities. nap(); // enlève un élément du tampon message = (Date)buffer. remove(); } } } Systèmes d'exploitation - SUPINFO Page 6. 40 Pierre Dimo - 2005

Le tampon de taille finie (suite) public class Factory { public static void main(String

Le tampon de taille finie (suite) public class Factory { public static void main(String args[]) { Buffer buffer = new Bounded. Buffer(); // 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(); } } Systèmes d'exploitation - SUPINFO Page 6. 41 Pierre Dimo - 2005

class Buffer { private static final MAX_AVAILABLE = 100; private final Semaphore available =

class Buffer { private static final MAX_AVAILABLE = 100; private final Semaphore available = new Semaphore(MAX_AVAILABLE, true); public Object get. Item() throws Interrupted. Exception { available. acquire(); // si disponibilité obtient le sémaphore, sinon bloque le thread return get. Next. Available. Item(); } public void put. Item(Object x) { if (mark. As. Unused(x)) available. release(); // libère le sémaphore } protected Object[] items =. . . Les objets qu’il faut gérer de manière protégée boolean[] used = new boolean[MAX_AVAILABLE]; protected synchronized Object get. Next. Available. Item() { for (int i = 0; i < MAX_AVAILABLE; ++i) { if (!used[i]) { used[i] = true; return items[i]; } } return null; // not reached } protected synchronized boolean mark. As. Unused(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; } } Systèmes d'exploitation - SUPINFO Page 6. 42 Pierre Dimo - 2005

Lecture et écriture dans une base de données public class Reader implements Runnable {

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. obtenir. Read. Lock(); // Accès accordé // Lecture db. liberer. Read. Lock(); } } } Systèmes d'exploitation - SUPINFO Page 6. 43 Pierre Dimo - 2005

Lecture et écriture dans une BD (suite) public class Writer implements Runnable { //

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. obtenir. Write. Lock(); // Accès accordé // Ecrit db. liberer. Write. Lock(); } } } Systèmes d'exploitation - SUPINFO Page 6. 44 Pierre Dimo - 2005

Lecture et écriture dans une BD (suite) public interface RWLock { public abstract void

Lecture et écriture dans une BD (suite) public interface RWLock { public abstract void obtenir. Read. Lock(); public abstract void obtenir. Write. Lock(); public abstract void liberer. Read. Lock(); public abstract void liberer. Write. Lock(); } Systèmes d'exploitation - SUPINFO Page 6. 45 Pierre Dimo - 2005

Lecture et écriture dans une BD (suite) La base de données public class Database

Lecture et écriture dans une BD (suite) La base de données public class Database implements RWLock { private int reader. Count; private Semaphore mutex; private Semaphore db; public Database() { reader. Count = 0; mutex = new Semaphore(1); db = new Semaphore(1); } public int obtenir. Read. Lock() { /* voir suite */ } public int liberer. Read. Lock() {/* voir suite */ } public void obtenir. Write. Lock() {/* voir suite */ } public void liberer. Write. Lock() {/* voir suite */ } } Systèmes d'exploitation - SUPINFO Page 6. 46 Pierre Dimo - 2005

Lecture et écriture dans une BD (suite) Méthodes utilisée pour la lecture public void

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

Lecture et écriture dans une BD (suite) Méthodes utilisée pour l’écriture public void obtenir.

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

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

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

Le problème des philosophes chinois - enoncé ● Énoncé – – – ● 5

Le problème des philosophes chinois - enoncé ● Énoncé – – – ● 5 philosophes réfléchissent autour d’une 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 l’ordre gauche -> droite ● Seulement 2 philosophes peuvent manger en même temps ● Les deux philosophes qui mangent ne peuvent pas se trouver l’un a coté de l’autre 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 d’une large classe de problèmes de synchronisation de processus Systèmes d'exploitation - SUPINFO Page 6. 50 Pierre Dimo - 2005

Le problème des philosophes chinois (suite) 1. On numérote les baguettes et les philosophes

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 + 1. 2. Chaque baguette joue le rôle d’un 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. Systèmes d'exploitation - SUPINFO Page 6. 51 Pierre Dimo - 2005

Le problème des philosophes chinois (suite) ● On peut essayer de traiter le problème

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… } Systèmes d'exploitation - SUPINFO Page 6. 52 Pierre Dimo - 2005

Le problème des philosophes chinois (suite) ● ● La solution ne satisfait pas car

Le problème des philosophes chinois (suite) ● ● La solution ne satisfait pas car elle peut conduire à un blocage : c’est 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 l’opération doit être réalisée de manière « atomique » (section critique). Utiliser une règle d’assymetrie 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 qu’aucun philosophe ne meurt de faim… parce qu’il n’a jamais accès aux Systèmes d'exploitation - SUPINFO Page 6. 53 Pierre Dimo - 2005

Problème des sémaphores ● ● Des erreurs de programmation peuvent se produire dans la

Problème des sémaphores ● ● Des erreurs de programmation peuvent se produire dans la logique d’acquisition 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 l’utilisateur), c. a. d. seulement lorsque sont exécutées certaines séquences qui n’ont pas un caractère régulier Systèmes d'exploitation - SUPINFO Page 6. 54 Pierre Dimo - 2005

Problème des sémaphores (suite) ● Exemples d’erreurs – Inversion de l’ordre des accès aux

Problème des sémaphores (suite) ● Exemples d’erreurs – Inversion de l’ordre des accès aux sémaphores : plusieurs processus peuvent exécuter leur section critique en même temps… ● ● ● – Demande double du même sémaphore : blocage ● ● ● – smphr. liberer() Section. Critique() smphr. obtenir() sectioncritique() smphr. obtenir() Oubli d’une des demandes (obtenir ou liberer) : tout peut arriver ! Systèmes d'exploitation - SUPINFO Page 6. 55 Pierre Dimo - 2005

Les moniteurs : une approche différente de la synchronisation ● ● Le moniteur est

Les moniteurs : une approche différente de la synchronisation ● ● Le moniteur est un module qui réalise une abstraction sécurisée de l’exécution des fils Dans le moniteur, un seul fil peut être actif à la fois, l’exclusion réciproque des fils est assurée par le moniteur Les structures internes du moniteur ne sont pas visibles de l’exté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 L’opération Wait met en attente le fil dans une queue si la condition X est remplie jusqu’au moment ou un autre fil envoie le message Signal. Si aucun fil n’est 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. Systèmes d'exploitation - SUPINFO Page 6. 56 Pierre Dimo - 2005

Moniteur avec variables de condition Queue des demandes d’accès au moniteur Données partagées Opérations

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

Les philosophes : solution avec moniteur. monitor Philosophes { int[] etat = new int[5];

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 Systèmes d'exploitation - SUPINFO Page 6. 58 Pierre Dimo - 2005

Les Philosophes – solution avec MONITEUR (suite) ● Le rôle des variables : int[]

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 d’enregistrer l’état d’attente lorsque le processus ne peut pas continuer Notre solution impose que la personne qui mange soit Pagene 6. 59 sont pas en train de Pierre Dimo - 2005 Systèmes d'exploitation entourée- SUPINFO de personne qui ●

Les philosophes : solution avec moniteur (suite). public levebag(int i) { etat[i] = FAIM;

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) && != MANGE) ) { setat[i] = MANGE; self[i]. signal; } } Systèmes d'exploitation - SUPINFO (etat[i] == FAIM) && Page 6. 60 (state[(i + 1) % 5] Pierre Dimo - 2005

Les philosophes : solution avec moniteur (suite). Avant de manger, un philosophe doit essayer

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 l’ordre suivant : philor. levebag(i); mange() ; // opération se déroulant sans risque philo. posebag(i); ou mntr est une instance de la classe PHILOSOPHES ● Systèmes d'exploitation - SUPINFO Page 6. 61 Pierre Dimo - 2005

Méthodes spécifiques ● ● ● Méthodes spécifiques : synchronized, wait(), notify() Notifications multiples Synchronization

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

Le qualificatif synchronized ● ● Java associe à chaque objet un verrou. Lors de

Le qualificatif synchronized ● ● Java associe à chaque objet un verrou. Lors de l’invocation des méthodes de l’objet 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 l’accès à la méthode verrouillée (entry set). Le verrou est libéré lorsque le thread quitte la méthode synchronisée Page 6. 63 prendra le contrôle. Pierre Pour la sélection du thread qui du Dimo - 2005 Systèmes d'exploitation - SUPINFO ●

Exemple « producteur / consommateur » Public synchronized void insert(Object o) { while (cmpt

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 qu’il n’y aura pas de « course » entre les deux pour modifier cmpt. Systèmes d'exploitation - SUPINFO Page 6. 64 Pierre Dimo - 2005

Problème posé par synchronized ● La solution exposée peut conduire à une situation de

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 à l’objet, car il est verrouillé ! Systèmes d'exploitation - SUPINFO Page 6. 65 Pierre Dimo - 2005

Solution : les méthodes wait() et notify() ● ● Chaque objet Java possède en

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 d’exécution. Si le thread entre dans une méthode synchronisée qu’il 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 l’ensemble d’attente (le wait set), le temps nécessaire pour que la condition qui l’a 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 d’attente Déplace le thread dans la liste des processus demandeurs d’accès au verrou (entry set) Change l’état du thread, de bloqué en exécutable Systèmes d'exploitation - SUPINFO Page 6. 66 Pierre Dimo - 2005

Producteur / conommateur : gestion du verrou Public synchronized void insert(Object o){ while (cmpt

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 d’attente } catch (Interrupted. Exception e){ } } cmpt++; // le thread peut continuer… tampon[i] = o; i = (i+1)%TAILLE_TAMPON; notify(); // annonce la fin de la méthode synchronisée } Systèmes d'exploitation - SUPINFO Page 6. 67 Pierre Dimo - 2005

Producteur / consommateur : gestion du verrou Public synchronized Object enleve(){ Object o; while

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 d’attente } catch(Interrupted. Exception 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; } Systèmes d'exploitation - SUPINFO Page 6. 68 Pierre Dimo - 2005

notify. All() ● ● ● Plusieurs threads peuvent se trouver dans la queue d’attente

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

Synchronisation des blocs ● ● Les procédures synchronisées peuvent être pénalisantes si elles sont

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 l’on peut appelé Verrou, comme dans l’exemple ci-dessous : Object Verrou = new Object(); ………… Public void une. Méthode() { section. Non. Critique() ; synchronized(Verrou) { section. Critique() } section. Non. Critique } Systèmes d'exploitation - SUPINFO Page 6. 70 Pierre Dimo - 2005

Gestion des interruptions ● ● Un thread peut être interrompu par un autre thread

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 l’arrête pas : l’arrêt effectif est de la responsabilité du thread interrompu, qui doit donc tester le fanion (is. Interrupted()) 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 » ). L’utilisation 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 n’a pas été fait dans les exemples précédents pour des raisons de clarté. Systèmes d'exploitation - SUPINFO Page 6. 71 Pierre Dimo - 2005

Quelques règles concernant la synchronisation des processus ● ● ● Un thread qui contrôle

Quelques règles concernant la synchronisation des processus ● ● ● Un thread qui contrôle un verou d’un objet, peut entrer dans une autre méthode synchronisée et prendre le contrôle d’un 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 l’objet 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. qu’un autre thread exécute une méthode synchronisée). Si la queue d’attente (le wait set) d’un objet est vide, les méthodes notify() et notifyall() sont sans effet. Les méthodes wait(), notify() et notify. All() peuvent être invoquées seulement à partir de méthodes synchronisées. . Systèmes d'exploitation - SUPINFO Page 6. 72 Pierre Dimo - 2005