Exercices INF5171 : série #3 (Automne 2012) 1. Un moniteur pour gérer des ressources Le moniteur MPD 1 présente une première version d'un moniteur, exprimé en pseudo-mpd, pour gérer des ressources le nombre de ressources à gérer est spécié au moment de la création du moniteur. Le désavantage de cette version est qu'un thread pourrait tenter de libérer des ressources... qu'il n'a pas obtenues, par exemple : OK : obtenir( 4 );...; liberer( 3 );...; liberer( 1 ); Pas OK : obtenir( 4 );...; liberer( 3 );...; liberer( 2 ); Le moniteur MPD 2 présente une deuxième version, qui permet cette fois d'assurer qu'un thread ne libère pas incorrectement des ressources qu'il n'a pas acquises. Pour simplier, on utilise simplement une assertion pour vérier que le Permis obtenu possède encore les ressources allouées. L'interface Java 1 dénit un type GestionnaireRessources pour lequel nous examinerons diérentes mises en oeuvre. a. La classe Java 1 présente un squelette pour une classe GestionnaireMoniteur qui met en oeuvre l'interface GestionnaireRessources en utilisant uniquement les primitives du langage Java mot-clé synchronized, méthodes wait et notifyall. b. La classe Java 2 présente un squelette pour une classe GestionnaireSem qui met en oeuvre l'interface GestionnaireRessources en utilisant la classe Semaphore dénie dans la bibliothèque java.util.concurrent. c. La classe Java 3 présente un squelette pour une classe GestionnaireCond qui met en oeuvre l'interface GestionnaireRessources en utilisant l'interface et les classes Lock, ReentrantLock et Condition de la bibliothèque java.util.concurrent. Complétez chacune de ces classes. 1
Moniteur MPD 1 Une première version d'un moniteur pour gérer des ressources. monitor GestionnaireRessources op obtenir( int n ); op liberer( int n ); body GestionnaireRessources( int nbressources ) cond attente; int nbdisponibles = nbressources; proc obtenir( n ) assert( 1 <= n & n <= nbressources ); while ( n > nbdisponibles ) wait(attente); nbdisponibles -= n; proc liberer( n ) assert( 1 <= n ); nbdisponibles += n; signal_all( attente ); end 2
Moniteur MPD 2 Une deuxième version d'un moniteur pour gérer des ressources. monitor GestionnaireRessources type Permis; # Type prive, opaque. op obtenir( int n ) returns Permis p op liberer( Permis p, int n ); body GestionnaireRessources( int nbressources ) type Permis = ptr int; cond attente; int nbdisponibles = nbressources; proc obtenir( n ) returns p assert( 1 <= n & n <= nbressources ); end while ( n > nbdisponibles ) wait(attente); nbdisponibles -= n; p = new(int); p^ = n; proc liberer( p, n ) assert( 1 <= n & n <= p^ ); nbdisponibles += n; p^ -= n; signal_all( attente ); Interface Java 1 Interface GestionnaireRessources. interface GestionnaireRessources public Permis obtenir( int n ); void liberer( Permis p, int n ); 3
Classe Java 1 Classe GestionnaireMoniteur. class GestionnaireMoniteur implements GestionnaireRessources private int nbressources; private int nbdisponibles; GestionnaireMoniteur( int nbressources ) this.nbressources = nbressources; this.nbdisponibles = nbressources; class PermisPrive implements Permis private int qte; PermisPrive( int n ) this.qte = n; public int quantite() return qte; synchronized public Permis obtenir( int n ) assert( 1 <= n && n <= nbressources ); synchronized public void liberer( Permis p, int n ) assert( 1 <= n && n <= p.quantite() ); 4
Classe Java 2 Classe GestionnaireSem. import java.util.concurrent.*; class GestionnaireSem implements GestionnaireRessources Semaphore sem; private int nbressources; GestionnaireSem( int nbressources ) this.nbressources = nbressources; this.sem = new Semaphore( nbressources ); class PermisPrive implements Permis private int qte; PermisPrive( int n ) this.qte = n; public int quantite() return qte; public Permis obtenir( int n ) assert( 1 <= n && n <= nbressources ); public void liberer( Permis p, int n ) assert( 1 <= n && n <= p.quantite() ); 5
Classe Java 3 Classe GestionnaireCond. import java.util.concurrent.locks.*; class GestionnaireCond implements GestionnaireRessources Lock mutex = new ReentrantLock(); Condition disponible = mutex.newcondition(); private int nbressources; private int nbdisponibles; GestionnaireCond( int nbressources ) this.nbressources = nbressources; this.nbdisponibles = nbressources; class PermisPrive implements Permis private int qte; PermisPrive( int n ) this.qte = n; public int quantite() return qte; public Permis obtenir( int n ) assert( 1 <= n && n <= nbressources ); public void liberer( Permis p, int n ) assert( 1 <= n && n <= p.quantite() ); 6
2. Un moniteur pour gérer des piles L'interface Java 2 dénit un type Pile pour lequel nous examinerons diérentes mises en oeuvre sous forme de moniteur. Comme pour l'exercice précédent, la description initiale du moniteur sera donnée par un monitor exprimé en pseudo-mpd. On dit d'un type de données modélisant une forme de collection un regroupement d'éléments du même type, par exemple, ensembles, séquences, piles qu'il permet de dénir des collections bornées lorsqu'il existe une limite a priori sur le nombre d'éléments que peut contenir la collection, limite généralement spéciée au moment de la création de la collection. Lorsqu'aucune limite n'existe a priori (sauf, évidemment, l'espace mémoire disponible sur la machine ;), on dit alors que le type permet de dénir des collections non bornées. Le moniteur MPD 3 présente un moniteur MPD pour gérer des piles non bornées pouvant être utilisées par des threads. Lorsqu'un thread tente d'obtenir un élément de la pile avec depiler et que la pile est vide, alors le thread bloque jusqu'à ce qu'un élément soit empilé. Comme il n'y a pas de limite sur le nombre d'éléments pouvant être empilés, alors l'opération empiler ne bloque jamais. Quant au moniteur MPD 4, il présente un moniteur MPD pour gérer des piles bornées. Comme pour les piles non bornées, un thread qui tente d'obtenir un élément lorsque la pile est vide bloque jusqu'à ce qu'un élément soit empilé. Par contre, Comme il y a une limite sur le nombre d'éléments pouvant être empilés, alors un thread qui tente d'empiler alors que la pile est pleine va bloquer, jusqu'à ce qu'un élément ait été retiré. Remarques : De telles piles pourraient être utilisées pour mettre en oeuvre un sac de tâches avec une stratégie LIFO. Pour être complet, il aurait aussi été préférable de dénir une opération pour déterminer si la pile est vide, ou encore pour tenter de dépiler et ne pas bloquer si elle est vide. Toutefois, pour simplier l'exemple, seules les opérations empiler et depiler seront examinées. Un certain nombre de squelettes de classe vous sont fournis, que vous devez compléter : a. La classe Java 4 présente un squelette pour une classe PileNonBornee qui met en oeuvre l'interface Pile avec des piles non bornées et ce en utilisant uniquement les primitives du langage Java mot-clé synchronized, méthodes wait et notifyall. b. La classe Java 5 présente un squelette pour une classe PileBornee qui met en oeuvre l'interface Pile avec des piles bornées et ce en utilisant l'interface et les classes Lock, ReentrantLock et Condition de la bibliothèque java.util.concurrent. c. La classe Java 6 présente un squelette pour une classe PileBornee qui met en oeuvre l'interface Pile avec des piles bornées et ce en utilisant les Semaphores de la bibliothèque java.util.concurrent. pile-bornee-sem.java d. La classe Java 7 présente un squelette pour une classe PileBornee qui met en oeuvre l'interface Pile avec des piles bornées et ce en utilisant uniquement les primitives du langage Java mot-clé synchronized, méthodes wait et notifyall. Remarque : Malheureusement, cette classe ne fonctionnera pas, à moins d'introduire des classes auxiliaires complexes... Pourquoi? 7
Interface Java 2 Interface Pile. interface Pile void empiler( int v ); int depiler(); Moniteur MPD 3 Un moniteur pour gérer une PileNonBornee. monitor PileNonBornee # Pile non bornee: empiler ne bloque jamais. op empiler( int elem ); # depiler bloque si la pile est vide. op depiler() returns int sommet; body PileNonBornee() cond pasvide; int elems[0:*]; int nbelements = 0; # Illegal... mais pour simplifier la presentation # Represente un tableau <<non-borne>> (sic). # Borne a 0 pour faciliter la correspondance avec Java. proc empiler( elem ) elems[nbelements++] = elem; signal(pasvide); proc depiler() returns sommet while ( nbelements == 0 ) wait(pasvide); sommet = elems[--nbelements]; end 8
Moniteur MPD 4 Un moniteur pour gérer une PileBornee. monitor PileBornee # Pile bornee: empiler bloque si la pile est pleine. op empiler( int elem ); # depiler bloque si la pile est vide. op depiler() returns int sommet; body PileBornee( int taillemax ) cond paspleine; cond pasvide; int elems[0:taillemax-1]; int nbelements = 0; proc empiler( int elem ) while( nbelements == taillemax ) wait(paspleine); elems[nbelements++] = elem; signal(pasvide); proc depiler() returns sommet while ( nbelements == 0 ) wait(pasvide); sommet = elems[--nbelements]; signal(paspleine); end 9
Classe Java 4 Classe PileNonBornee. import java.util.vector; class PileNonBornee implements Pile private Vector<Integer> elems; PileNonBornee() this.elems = new Vector<Integer>(); synchronized public void empiler( int elem ) synchronized public int depiler() 10
Classe Java 5 Classe PileBornee. import java.util.concurrent.locks.*; class PileBornee implements Pile private int elems[]; private int nbelements; Lock mutex = new ReentrantLock(); Condition paspleine = mutex.newcondition(); Condition pasvide = mutex.newcondition(); PileBornee( int taillemax ) this.elems = new int[taillemax]; this.nbelements = 0; public void empiler( int elem ) public int depiler() 11
Classe Java 6 Classe PileBorneeSem. import java.util.concurrent.*; class PileBorneeSem implements Pile private int elems[]; private int nbelements; Semaphore mutex = new Semaphore( 1 ); Semaphore semnboccupes; Semaphore semnblibres; private void P( Semaphore sem ) try sem.acquire(); catch( Exception e ) private void V( Semaphore sem ) sem.release(); PileBorneeSem( int taillemax ) this.elems = new int[taillemax]; this.nbelements = 0; this.semnboccupes = new Semaphore( 0 ); this.semnblibres = new Semaphore( taillemax ); public void empiler( int elem ) public int depiler() 12
Classe Java 7 Classe PileBorneePasok. class PileBorneePasok implements Pile private int elems[]; private int nbelements; Object paspleine = new Object(); Object pasvide = new Object(); PileBorneePasok( int taillemax ) this.elems = new int[taillemax]; this.nbelements = 0; synchronized public void empiler( int elem ) synchronized public int depiler() 13