commit c64e8caa21cc8d60063e784f655b7c9e5b41eef5 Author: Laureηt Date: Wed Jun 21 20:19:26 2023 +0200 init diff --git a/TP1/CC_README.html b/TP1/CC_README.html new file mode 100644 index 0000000..0606197 --- /dev/null +++ b/TP1/CC_README.html @@ -0,0 +1,103 @@ + + + + + + + + + + +

Concurrence et cohérence

+

Objectifs

+ +

Préparation

+

Cette partie présente quelques constructions et outils disponibles sur la plateforme Java, en se limitant aux éléments nécessaires à la réalisation des exercices.

+

Les activités (threads) Java

+

La brève présentation qui suit peut être illustrée et par la lecture de la partie correspondante du cours sur les processus légers (6ème partie, planches 10-13). Les planches suivantes et la documentation présentent plus de détails, leur lecture n'est pas nécessaire dans l'immédiat.

+ +

Les planches 10 et 11 fournissent des exemples simples du schéma standard de création de threads

+

Le modèle de cohérence Java

+

Par défaut, Java n'offre pas de garantie de cohérence pour les variables partagées, utilisées simultanément par un ensemble de threads. En particulier, les instructions sont susceptibles d'être réordonnées, et la cohérence des caches n'est pas assurée.

+

Cependant Java offre quelques outils de base pour faciliter la programmation multiactivités avec des variables partagées :

+ +

Les verrous Java

+

Historiquement, il s'agit du premier outil de synchronisation proposé en Java, disponible dès les premières versions du langage. Le service rendu, très élémentaire, s'avère pratique et adapté pour exprimer l'exclusion mutuelle, ce qui fait qu'il reste largement utilisé, même actuellement, car la simple exclusion mutuelle représente le schéma de synchronisation le plus souvent rencontré. En revanche, il se révèle lourd et malcommode dès qu'il s'agit de réaliser des schémas un peu plus évolués que l'exclusion mutuelle. D'où les objets de synchronisation plus classiques et robustes apparus à partir de la version 5 de Java.

+

La rapide présentation qui suit porte essentiellement sur la syntaxe. Elle peut être complétée par la lecture de la partie correspondante du cours sur les processus légers (planches 36-40) pour les notions et sur la documentation Java en ligne pour les détails techniques.

+

Principe

+

Tout objet Java peut être utilisé comme un verrou pour contrôler l'accès à une section critique.

+

Syntaxe

+ +

Autres classes, méthodes et remarques utiles

+ +

Exercices

+

Efficacité de la parallélisation

+

Il s'agit d'évaluer le gain de temps apporté par la décomposition d'un traitement en plusieurs threads. On considère le traitement réalisé par la classe IncrMes fournie dans l'archive, qui consiste en une boucle qui incrémente un compteur global. Le nombre important d'itérations est destiné à permettre des mesures significatives, mais va nécessiter l'emploi d'entiers longs.

+

Comparer le temps d'exécution de l'exécution séquentielle de la boucle (lignes 93-107 du code fourni) avec le temps d'exécution d'une application où N threads effectuent chacun 1/N ème de l'itération, N étant un paramètre fourni au lancement de l'application (lignes 114-117 du code fourni).

+ +

Coût de la cohérence

+

Vérifier la correction des résultats obtenus par l'application précédente. Pour cela, chaque thread affichera la valeur du compteur avant de démarrer sa (fraction de) boucle, ainsi que la valeur du compteur après. (Il suffit pour cela de décommenter les affichages placés dans la méthode run(), à la fin du texte du programme.)

+ +

Supplément

+

L'archive fournie contient une classe PCA, qui propose un exercice supplémentaire, dans un but de variété. Cet exercice porte sur les mêmes notions que les exercices autour de la classe IncrMes, et n'introduit rien de nouveau.

+

Tester les performances d'applications concurrentes en Java : quelques remarques pratiques

+ + + diff --git a/TP1/CC_README.md b/TP1/CC_README.md new file mode 100644 index 0000000..79ece01 --- /dev/null +++ b/TP1/CC_README.md @@ -0,0 +1,183 @@ +# Concurrence et cohérence + +## Objectifs + +- évaluer le gain, le coût et le contexte approprié à la mise en œuvre multi-activités + d'un traitement +- mettre en évidence les problèmes de cohérence induits par + _ l'exécution d'activités concurrentes + _ les mécanismes d'optimisation matériels (caches, pipelines) et logiciels + (réordonnancement des instructions) + +## Préparation + +Cette partie présente quelques constructions et outils disponibles sur la plateforme Java, +en se limitant aux éléments nécessaires à la réalisation des exercices. + +### Les activités (threads) Java + +La brève présentation qui suit peut être illustrée et par la lecture de la partie +correspondante du cours sur les processus légers (6ème partie, planches 10-13). Les planches +suivantes et la documentation présentent plus de détails, leur lecture n'est +pas nécessaire dans l'immédiat. + +- la classe **`Thread`**, intégrée au langage (paquetage `java.lang`), permet de _définir_ + un processus (léger), ou thread au sein d'une application Java (JVM) +- la classe Thread fournit (en particulier) + - une méthode **`start`** qui permet de _lancer_ l'instance de thread auquel elle est appliquée + - une méthode **`join`** (`void join() throws InterruptedException`), + qui permet d'attendre la terminaison l'instance de thread auquel elle est appliquée + _ une méthode de classe, `static Thread currentThread()` qui fournit la référence du + thread en cours d'exécution + _ une méthode de classe, `static void sleep(long ms) throws InterruptedException` qui + suspend le thread appelant pour une durée de `ms` millisecondes +- le constructeur de la classe `Thread` prend un paramètre d'une classe implémentant + l'interface **`Runnable`**. Cette interface expose une méthode **`public void run()`**. + La méthode `run()` définit le code qui sera exécuté par le thread correspondant. + +**Les planches 10 et 11 fournissent des exemples simples du schéma standard de création de threads** + +### Le modèle de cohérence Java + +Par défaut, Java n'offre pas de garantie de cohérence pour les variables partagées, +utilisées simultanément par un ensemble de threads. En particulier, les instructions +sont susceptibles d'être réordonnées, et la cohérence des caches n'est pas assurée. + +Cependant Java offre quelques outils de base pour faciliter la programmation +multiactivités avec des variables partagées : + +- le mot-clé **`volatile`** associé à un attribut élémentaire ou une référence + garantit que (tout se passe comme si) les accès à cet attribut ne sont pas effectués dans le cache, mai + directement en mémoire, ce qui permet de considérer que les accès concurrents ont été + effectués en suivant un ordre total global. En outre, l'accès à des variables `volatile` inhibe le réordonnancement + des instructions +- le paquetage `java.util.concurrent.atomic` propose un ensemble de classes « atomiques », + qui fournissent un ensemble de méthodes dont l'exécution est garantie sans interférences. + La différence avec des méthodes `synchronized` (vues tout de suite après) est que + l'absence d'interférences est obtenue sans qu'il y ait à faire attendre (bloquer) des + activités en conflit. L'algorithmique de la mise en œuvre de tels objets est abordée dans + la dernière partie du cours. La documentation Java en ligne fournit le détail de ces classes + et de leur interface. + +### Les verrous Java + +Historiquement, il s'agit du premier outil de synchronisation proposé en Java, disponible +dès les premières versions du langage. Le service rendu, très élémentaire, s'avère pratique +et adapté pour exprimer l'exclusion mutuelle, ce qui fait qu'il reste largement utilisé, +même actuellement, car la simple exclusion mutuelle représente le schéma de synchronisation +le plus souvent rencontré. En revanche, il se révèle lourd et malcommode dès qu'il +s'agit de réaliser des schémas un peu plus évolués que l'exclusion mutuelle. D'où les objets +de synchronisation plus classiques et robustes apparus à partir de la version 5 de Java. + +La rapide présentation qui suit porte essentiellement sur la syntaxe. Elle peut être +complétée par la lecture de la partie correspondante du cours sur les processus légers +(planches 36-40) pour les notions et sur la documentation Java en ligne pour les détails +techniques. + +#### Principe + +**Tout objet** Java peut être utilisé comme un verrou pour contrôler l'accès à une section +critique. + +#### Syntaxe + +- Le mot-clé **`synchronized`** permet de définir très simplement une section critique + contrôlée par (le verrou d')un objet `unObj`, avec la syntaxe suivante + **`synchronized`** `(unObj) { section critique }` +- Le mot-clé **`synchronized`** peut aussi être utilisé pour qualifier une méthode. Il indique + alors que la méthode sera exécutée en exclusion mutuelle, et que (le verrou de l'instance + de) l'objet fournissant cette méthode est utilisé pour contrôler cette exclusion mutuelle. + +- Enfin, il est possible de qualifier des méthodes de classe (statiques) comme **`synchronized`**. + Dans ce cas, le verrou est associé à la classe, non à ses instances. + +### Autres classes, méthodes et remarques utiles + +- la classe `System` fournit deux méthodes, `System.nanoTime()` et `System.currentTimeMillis()` qui fournissent une durée écoulée (en ns et ms) depuis une date d'origine non spécifiée. La différence entre les valeurs retournées par deux appels successifs permet d'évaluer le temps écoulé entre ces deux appels. +- Le constructeur de la classe `Thread` peut prendre un paramètre de classe `String`, + qui permet de donner un nom à l'instance de Thread créée. Le nom peut être accédé via les + méthodes `getName()` et `setName()` +- Selon votre configuration, il est possible que le format par défaut + pour les fichiers source Java soit le format ASCII. Dans ce cas, des erreurs + apparaitront lors de la compilation des fichiers de l'archive, qui sont codés en UTF8. + Pour remédier à cela, il est possible de positionner la variable d'environnement + JAVA_TOOL_OPTIONS (en bash : `export JAVA_TOOL_OPTIONS=-Dfile.encoding=UTF8`), + ou encore de lancer la compilation avec l'option `-encoding UTF8`, ce qui donne ici : + javac -encoding UTF8 \*.java +- l'interpréteur Java (commande `java`) founit deux options qui ne seront pas forcément nécessaires + ici, mais qui peuvent être utiles dans un contexte d'évaluation de performances : + - `-Xint` force le passage en mode interprété pur (pas de compilation à la volée, ni + par conséquent d'optimisation) + - `-Xprof` fournit des statistiques sur les temps d'exécution des threads. + +## Exercices + +### Efficacité de la parallélisation + +Il s'agit d'évaluer le gain de temps apporté par la décomposition d'un traitement +en plusieurs threads. +On considère le traitement réalisé par la classe `IncrMes` fournie dans l'archive, qui +consiste en une boucle qui incrémente un compteur global. Le nombre important d'itérations +est destiné à permettre des mesures significatives, mais va nécessiter l'emploi d'entiers +longs. + +Comparer le temps d'exécution de l'exécution séquentielle de la boucle (lignes 93-107 du +code fourni) avec le temps d'exécution d'une application où N threads effectuent chacun +1/N ème de l'itération, N étant un paramètre fourni au lancement de l'application (lignes 114-117 du code fourni). + +- Quel résultat « idéal » peut-on a priori espérer ? +- Mesurer le temps d'exécution réel, en fonction de N (en faisant varier N entre 1 et 50, + sans nécessairement prendre toutes les valeurs :) ) + + - Expliquez les différences observées entre le temps mesuré et le temps attendu. + - Evaluer le surcoût induit par la gestion des threads, au moins en principe + (Il est possible que cette valeur soit trop faible pour être mesurée ainsi, les + différents mécanismes d'optimisation au niveau du matériel ou du compilateur et le + contexte d'exécution nuisant à la précision des mesures, cf remarque finale) + +### Coût de la cohérence + +Vérifier la correction des résultats obtenus par l'application précédente. Pour cela, +chaque thread affichera la valeur du compteur avant de démarrer sa (fraction de) boucle, +ainsi que la valeur du compteur après. (Il suffit pour cela de décommenter les affichages +placés dans la méthode run(), à la fin du texte du programme.) + +- Quelles seront _a priori_ les valeurs affichées dans le cas où il n'y a pas préemption + du processeur entre threads ? +- Quelles seront _a priori_ les valeurs affichées dans le cas où la gestion des activités + partage le temps processeur par quantum de temps entre threads ? +- Quelle est la politique effectivement suivie par la JVM utilisée pour le test ? +- La valeur finale du compteur devrait être égale au nombre total d'itérations. Vérifier + que ce n'est pas le cas avec la version actuelle, et expliquer pourquoi. +- Afin de garantir la cohérence du résultat final, on effectue les incrémentations du + compteur en exclusion mutuelle, en plaçant l'incrémentation dans un bloc `synchronized`, + associé à un objet global quelconque. (Déclarer par exemple un attribut + `static Object mutex = new Object();` dans la classe principale). Vérifier que le résultat + est maintenant effectivement correct, et évaluer le coût de l'utilisation de ce mécanisme. + + - en plaçant uniquement l'incrémentation de la boucle interne dans le bloc `synchronized` + - en plaçant la boucle interne dans le bloc `synchronized` + +- La correction du résultat est-elle garantie a priori si l'on utilise un objet de la classe + `java.util.concurrent.atomic.AtomicLong` pour le compteur ? Argumenter, puis vérifier + cet a priori. Evaluer le coût de l'utilisation de ce mécanisme +- La correction du résultat est-elle garantie a priori si l'on déclare le compteur + comme `volatile` ? Argumenter, puis vérifier cet a priori. Evaluer le coût de + l'utilisation de ce mécanisme. +- Conclure globalement sur les conditions d'utilisation (ou pas) de ces différents mécanismes. + +### Supplément + +L'archive fournie contient une classe PCA, qui propose un exercice supplémentaire, dans un +but de variété. Cet exercice porte sur les mêmes notions que les exercices autour de la +classe `IncrMes`, et n'introduit rien de nouveau. + +## Tester les performances d'applications concurrentes en Java : quelques remarques pratiques + +- sources de perturbation : cache, compilateur à la volée, ramasse miettes et optimiseur, + charge de l'environnement (matériel, réseau) + -> répéter les mesures et écarter les valeurs extrêmes (en général, les premières mesures). + L'application fournie répète les mesures 10 fois par défaut, ce qui est un paramètre + raisonnable (qui peut éventuellement être un peu réduit). +- tester sur des volumes de données significatifs +- connaître le nombre de processeurs réels disponibles diff --git a/TP1/IncrMes.java b/TP1/IncrMes.java new file mode 100644 index 0000000..3c426e7 --- /dev/null +++ b/TP1/IncrMes.java @@ -0,0 +1,189 @@ +import java.util.concurrent.atomic.AtomicLong; + +// V0.3 - PM 17/09/21 + +interface Incrémenteur extends Runnable{ + void incr(); + /* Boucle d'incrémentation effectuée par un processus (thread). + * Le principe/objectif est de réaliser cette boucle, comportant un même nombre + * d'incrémentations en suivant différents schémas pour maintenir la cohérence + * (ou pas) et de comparer ces schémas en termes de correction et d'efficacité + */ +} + +public class IncrMes { + + // static long cpt = 0L; + static volatile long cpt = 0L; + // static final long NB_IT = 1000L; //Nb d'itérations de la boucle externe + // static final long NB_IT_INTERNES = 1000000L; //Nb d'itérations de la boucle interne + static final long NB_IT = 10L; //Nb d'itérations de la boucle externe + static final long NB_IT_INTERNES = 100L; //Nb d'itérations de la boucle interne + static long Attente_ms = 2L; + static final int Attente_nano = 0; + static Thread[] activités; // Tableau des processus + static Object mutex = new Object(); // pour les blocs synchronized + + static void lancer(int nbA, Incrémenteur r) { + // Initialisation du/des compteur(s) incrémentés + cpt = 0L; + /* Création et lancement des threads. + * Chaque thread exécute le même code, qui est la méthode run() d'une classe + * implantant un Incrémenteur (autrement dit un schéma de réalisation particulier + * de la boucle dincrémentation) + */ + + for (int i = 0; i < nbA ; i++) { + try { + IncrMes.activités[i] = new Thread(r,"t"+i); + } + catch (Exception e) + { + System.out.println(e); + } + IncrMes.activités[i].start(); + } + } + + static void finir() { + // Attente de la terminaison des différents threads lancés + for (int i = 0; i < IncrMes.activités.length ; i++) { + try { + IncrMes.activités[i].join(); + } + catch (InterruptedException e) + { + System.out.println(e); + } + } + } + + public static void main(String[] args) { + int nbActivités = 3; + int nbMesures = 6; + int i = 0; + long départ, fin; + boolean mutex = true; + + // Chargement des paramètres saisis en ligne de commande + if (args.length == 3) { + try { + nbActivités = Integer.parseInt (args[0]); + Attente_ms = Integer.parseInt (args[1]); + nbMesures = Integer.parseInt (args[2]); + } + catch (NumberFormatException nfx) { + Attente_ms = 0; + } + if ((nbActivités < 1) || (Attente_ms < 1) || (nbMesures < 1)) { + System.out.println("Usage : IncrMes ," + +"avec Nb activités, durée pause, nbMesures >= 1"); + System.exit (1); + } + } else { + System.out.println("Nb d'arguments ≠ 3. Exécution avec 6 mesures, 3 activités et 2 ms de pause"); + } + + activités = new Thread[nbActivités]; + + for (int it=1; it<=nbMesures; it++) { // + // Mesure itérée, pour éviter des écarts trop importants sur les premières mesures + // (du fait de possibles optimisations : processeur, cache, compilateur). + // Idée = retenir une moyenne des dernières mesures. + + // Boucle séquentielle : NB_IT x NB_IT_INTERNES x nbActivités itérations + cpt=0L; + départ = System.nanoTime(); + for (long li = 0; li < nbActivités*IncrMes.NB_IT; li++) { + for (long j = 1; j<=IncrMes.NB_IT_INTERNES; j++) { + IncrMes.cpt=IncrMes.cpt+j/j; + // j/j pour tenter d'éviter les optimisations du compilateur + } + try { + Thread.sleep(IncrMes.Attente_ms, IncrMes.Attente_nano); + // sleep pour modéliser un temps de calcul sans conflit d'accès et... + // pour tenter d'éviter les optimisations du cache + } + catch (InterruptedException ie) + { + System.out.println("InterruptedException : " + ie); + } + } + fin = System.nanoTime(); + //System.out.print((fin-départ)/1000000L + ", "); + System.out.println("Durée exécution mono : " + (fin-départ)/1000000L); + System.out.println(); + + // Exemple de mesure avec IncrémenteurNonSync + départ = System.nanoTime(); + // Lancement de nbActivités processus effectuant + // chacun NB_IT x NB_IT_INTERNES itérations + lancer(nbActivités, new IncrémenteurNonSync()); + finir(); + fin = System.nanoTime(); + //System.out.print((fin-départ)/1000000L + ", "); + System.out.println("Durée exécution non synchronisée (ms): " + (fin-départ)/1000000L); + System.out.println(); + + // Compléter ici, sur le modèle précédent (lignes 112-120, (ou éditer la ligne 116) + //en définissant et mesurant différents Incrémenteur + // de manière analogue à ce qui est fait pour IncrémenteurNonSync + + } + } +} + +class IncrémenteurNonSync implements Incrémenteur { +// class IncrémenteurNonSync extends AtomicLong implements Incrémenteur { + /* Exemple d'incrémenteur : + -la méthode incr effectue les incrémentations sans gérer les conflis d'accès au compteur + - la méthode run() appelle incr() + */ + + public synchronized void incr_sync(long j){ + IncrMes.cpt=IncrMes.cpt+j/j; + } + + public synchronized void interne(long i) { + for (long j = 1; j<=IncrMes.NB_IT_INTERNES; j++) { + // incrémentation élémentaire + IncrMes.cpt=IncrMes.cpt+j/j; + } + } + + public void incr() { + // boucle imbriquée pour permettre (éventuellement) de tester différents + // grains de synchronisation + for (long i = 0L; i < IncrMes.NB_IT; i++) { + // boucle interne + for (long j = 1; j<=IncrMes.NB_IT_INTERNES; j++) { + // incrémentation élémentaire + incr_sync(j); + // IncrMes.cpt=IncrMes.cpt+j/j; + } + // interne(i); + try { + Thread.sleep(IncrMes.Attente_ms, IncrMes.Attente_nano); + } + // Attente depour éviter l'utilisation du cache et modéliser + // une partie du calcul sans conflit. + // Vous pouvez (éventuellement) commenter l'attente + // pour voir l'effet (intéressant) du mécanisme de cache, + // ou encore modifier la valeur de l'attente pour voir + // l'effet du grain de l'exclusion mutuelle sur les temps + // d'exécution + catch (InterruptedException ie) + { + System.out.println("InterruptedException : " + ie); + } + } + } + + public void run() { + // afficher éventuellement la valeur du compteur avant/après + // pour vérifier la cohérence des incrémentations + System.out.println("Thread " + Thread.currentThread().getName() + " part de : " + IncrMes.cpt); + this.incr(); + System.out.println("Thread " + Thread.currentThread().getName() + " finit à : " + IncrMes.cpt); + } +} \ No newline at end of file diff --git a/TP1/PCA.java b/TP1/PCA.java new file mode 100644 index 0000000..1e6ec82 --- /dev/null +++ b/TP1/PCA.java @@ -0,0 +1,49 @@ +// v0.2, 17/9/21 (PM) +/* Bonus (?) : schéma producteur-consommateur +La classe PCA suivante est une implémentation du schéma +producteur/consommateur, pour un unique producteur et un unique consommateur. L'algorithme +semble correct, et pourtant... le test montre un comportement incorrect. Expliquez et rectifiez le code en conséquence. +*/ + +public class PCA { + + static final int N = 10; + static int dépôt = 0; + static int retrait = 0; + static int[] tampon = new int[N]; + + public static void main(String[] args) { + Thread t1 = new Thread(new Producteur()); + Thread t2 = new Thread(new Consommateur()); + t1.start(); + t2.start(); + } +} + +class Producteur implements Runnable { +// dépose des suites de N entiers identiques, en incrémentant la valeur déposée à chaque nouvelle suite + public void run() { + for (;;) { + System.out.println("production..."); + while (PCA.dépôt-PCA.retrait>=PCA.N) {} + System.out.println("PCA.dépôt "+ PCA.dépôt/PCA.N); + PCA.tampon[PCA.dépôt%PCA.N] = PCA.dépôt/PCA.N; + PCA.dépôt++; + System.out.println("fait"); + } + } +} + +class Consommateur implements Runnable { + public void run() { + int item; + for (;;) { + System.out.println("repos..."); + while (PCA.dépôt<=PCA.retrait) {} + System.out.println("PCA.retrait"); + item=PCA.tampon[PCA.retrait%PCA.N]; + PCA.retrait++; + System.out.println("consommation "+item); + } + } +} \ No newline at end of file diff --git a/TP1/fig.png b/TP1/fig.png new file mode 100644 index 0000000..4d43e33 Binary files /dev/null and b/TP1/fig.png differ diff --git a/TP1/reponses.md b/TP1/reponses.md new file mode 100644 index 0000000..e5973f7 --- /dev/null +++ b/TP1/reponses.md @@ -0,0 +1,19 @@ +## Efficacité de la parallélisation + +1. On peut espérer que avec N threads le programme ira N fois plus vite (accélération linéaire). +2. On trace l'exécution réelle en fonction de N + 1. La différence s'explique du fait que le processeur passe du temps à changer d'états (stacks) plutôt que de passer du temps à réelement calculer (ici incrémenter un compteur) + 2. L'accélération est nettement visible pour un nombre faible de threads, celle-ci semble ensuite redevenir linéaire. On peut supposer que le mode synchronisé deviendrait plus lent que le mode séquentiel pour un très grand nombre de threads. (cf fig.png) + +## Coût de la cohérence + +1. Pour chaque le thread n°i on a alors "part de : NB_IT x NB_IT_INTERNES x i", "finit à NB_IT x NB_IT_INTERNES x (i+1)". +2. Si les thread sont commutés lors de leur exécution, alors il est peu probable d'obtenir les résultats de la questions précédente, surtout si le quanta est d'une durée inférieur à l'execution complète du thread. (On obtient des résultat ~proche de la question précédente, mais qui empirent avec le nombre de threads) +3. La deuxième solution semble être celle utilisée par Java. +4. La valeur finale diverge légèrement de la valeur théorique de NB_IT x NB_IT_INTERNES x nbActivités puisque, pour contourner les optimisations du compilateur on ajoute à chaque itération j/j à cpt au lieu de j. Cela introduit parfois des erreurs de calculs qui s'accumulent lors de l'execution globale du programme. +5. On essaie de rajouter synchronised à plusieurs endroits du code + 1. on obtient de résultat escompté mais le temps d'execution du programme est décuplé. + 2. on n'obtient pas le résultat escompté, les valeurs finales sont toujours différentes. +6. en utilisant AtomicLong les différences entre les départs et les arrivés des compteurs sont réduites mais subsistent toujours +7. Tout comme l'utilsation de synchronised sur l'incrémentation, déclarer la variable servant de compteur comme volatile permet d'obtenir un résultat correcte. +8. It Depends ™ diff --git a/TP1/result_nosync.txt b/TP1/result_nosync.txt new file mode 100644 index 0000000..a5d4fb8 --- /dev/null +++ b/TP1/result_nosync.txt @@ -0,0 +1 @@ +2655, 3102, 4231, 5456, 6549, 7541, 10138, 11893, 14396, 16122, 18336, 20809, 22951, 24691, 26523, 28462, 30112, 31924, 33608, 35593, 37080, 38809, 40741, 42474, 44225, 67106, 68852 \ No newline at end of file diff --git a/TP1/result_seq.txt b/TP1/result_seq.txt new file mode 100644 index 0000000..e755bff --- /dev/null +++ b/TP1/result_seq.txt @@ -0,0 +1 @@ +2559, 5213, 7668, 10294, 12815, 15239, 17886, 20374, 23023, 25629, 28091, 30578, 33087, 35766, 38372, 40875, 43494, 46042, 48450, 51239, 53631, 56108, 58746, 61173, 63680 \ No newline at end of file diff --git a/TP1/script.sh b/TP1/script.sh new file mode 100644 index 0000000..ff44cdc --- /dev/null +++ b/TP1/script.sh @@ -0,0 +1,5 @@ +for ((i = 1; i <= 25; ++i)); do + java IncrMes $i 1 1 >>result_nosync.txt + # java IncrMes $i 1 1 >>result_seq.txt + echo $i +done diff --git a/TP2/Mutex/MX_README.md b/TP2/Mutex/MX_README.md new file mode 100644 index 0000000..1256bdf --- /dev/null +++ b/TP2/Mutex/MX_README.md @@ -0,0 +1,63 @@ +# Exclusion mutuelle et synchronisation élémentaire + +## Objectifs + +- réfléchir sur les protocoles de base de gestion de la cohérence par exclusion mutuelle +- première approche de la synchronisation de processus au travers de l'utilisation d'un + mécanisme élémentaire de verrouillage/déverrouillage + +## Préparation + +### Les verrous Java (complément) + +**Au sein d'un bloc `synchronized`** contrôlé par un objet `unObj`, il est possible de +réaliser une synchronisation élémentaire au moyen des deux méthodes suivantes, fournies par +tout objet : + +- `unObj.wait()` libère l’accès exclusif à `unObj` et bloque l’activité appelante + en attente d’un réveil via une opération +- `unObj.notify()` ou `unObj.notifyAll()` qui réveille une (ou toutes les) activité(s) + bloquées sur `unObj.wait()` +- Remarques 1. les activités réveillées par `notify` sont mises en attente du verrou et ne poursuivent + effectivement qu'une fois celui-ci obtenu 2. une activité en bloquée sur `wait` _peut_ être réveillée spontanément... + Il est donc conseillé que l'attente soit gardée par une condition logique, et que cette + condition logique soit évaluée dans une boucle `while`, selon le schéma suivant : + `synchronized (obj) { while ( condition invalide ) { unObjbj.wait(); } }` + +## Exercices + +**Note** : Pour les besoins de ce TP, _la synchronisation des exercices qui suivent +doit être traitée en utilisant des **verrous**, à l'exclusion de tout autre outil +(sémaphores, moniteurs...)_. (Ces outils conviendraient, mais ce n'est pas l'objet de ces exercices.) + +### Exclusion mutuelle + +1. La classe `Peterson` fournie dans l'archive propose une implémentation du protocole + d'exclusion mutuelle de Peterson vu en cours. Vérifiez et complétez éventuellement cette + implémentation. + +2. L'ordre des deux premières affectations de la méthode entrer() (lignes 29 et 30 : + `Peterson.demande[id] = true;` et `Peterson.tour = di ;`) est-il important ? Pourquoi ? + +3) La classe `java.util.concurrent.atomic.AtomicBoolean` propose une implantation + logicielle de primitives de type TestAndSet, CompareAndSwap... - Implanter le protocole d'exclusion mutuelle pour N processus utilisant la primitive TestAndSet + présentée en cours (planche 23) - Réaliser une version vivace du protocole, garantissant que toute demande d'entrée + en section critique finira par être servie. - Comparer les performances des deux versions, entre elles et par rapport à une + version utilisant un bloc `synchronized` pour assurer l'exclusion mutuelle. - Comparer, pour 2 processus, ces versions à une version utilisant le protocole de Peterson + +### Schéma producteurs consommateurs + +4. Compléter la classe `TamponBorné` fournie, qui ne comporte aucune synchronisation, afin de + gérer convenablement les accès concurrents. + +- seules les méthodes `déposer` et `retirer` de la classe `TamponBorné` sont à modifier. Les + parties du code où l'on suggère d'intervenir sont signalées par un commentaire `//*** A compléter` +- le test intégré à l'application est conçu pour vérifier le comportement attendu suivant : + + * initialement, les consommateurs doivent attendre, car le tampon est vide + * les consommateurs retirent des entiers différents, dans l'ordre croissant, + sans trous dans la numérotation + * progressivement, de plus en plus de producteurs doivent attendre + +Vous pouvez commencer par exécuter le code fourni sans synchronisation, et constater qu'il +ne produit pas vraiment le comportement attendu... diff --git a/TP2/Mutex/Peterson.java b/TP2/Mutex/Peterson.java new file mode 100644 index 0000000..f79ed0d --- /dev/null +++ b/TP2/Mutex/Peterson.java @@ -0,0 +1,46 @@ +public class Peterson { + + static int tour = 0; + static boolean[] demande = { false, false }; + + public static void main(String[] args) { + Thread t1 = new Thread(new Proc(), "1"); + Thread t2 = new Thread(new Proc(), "0"); + t1.start(); + t2.start(); + } +} + +class Proc implements Runnable { + int id; // identifiant du Thread + int di; // identifiant de l'autre Thread + + public void run() { + id = Integer.parseInt(Thread.currentThread().getName()); + di = (id + 1) % 2; + + while (true) { // SC = Section Critique + System.out.println("Thread " + id + " est hors SC"); + entrer(); + System.out.println("Thread " + id + " est en SC"); + sortir(); + } + } + + synchronized void entrer() { + System.out.println("Thread " + id + " veut entrer en SC "); + Peterson.demande[id] = true; + Peterson.tour = di; + while ((Peterson.tour != id) && (Peterson.demande[di])) { + System.out.println( + (char) 27 + "[0;90m" + "Thread " + id + " attend la fin SC du Thread " + di + (char) 27 + "[0m"); + } + System.out.println("Thread " + id + " est entré en SC "); + } + + synchronized void sortir() { + System.out.println("Thread " + id + " veut sortir de SC "); + Peterson.demande[id] = false; + System.out.println("Thread " + id + " est sortit de SC "); + } +} \ No newline at end of file diff --git a/TP2/Mutex/ProdConso.java b/TP2/Mutex/ProdConso.java new file mode 100644 index 0000000..a4f53ee --- /dev/null +++ b/TP2/Mutex/ProdConso.java @@ -0,0 +1,170 @@ +import java.util.concurrent.atomic.AtomicBoolean; + +// v0.1, 25/09/20 (PM) + +class TamponBorné { + /* + * Pour simplifier la mise en œuvre du test, on se limite ici à un tampon + * d'entiers + */ + private int taille; // nombre de cases du tampon + private int nbOccupé = 0; // nombre d'items présents dans le tampon (vide initialement) + private int queue = 0; // queue de file (tampon circulaire), insertions + private int tête = 0; // tête de file (tampon circulaire), extractions + + private int trace = 0;// utile uniquement pour faciliter le test : + // on va considérer qu'on dépose les valeurs successives de trace... + + private int[] tampon; + + public TamponBorné(int t) { + taille = t; + tampon = new int[taille]; + } + + static AtomicBoolean occupied = new AtomicBoolean(true); + + synchronized public void déposer() { + + while (nbOccupé >= taille && !occupied.compareAndSet(false, true)) { + try { + System.out.println("wait prod"); + this.wait(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + // dépôt + tampon[queue] = trace++; // tampon[queue] = i; + queue = (queue + 1) % taille; + nbOccupé++; + + // affichage pour le test uniquement + String msg = (char) 27 + "[0;32m" + "P : " + (trace - 1); + if (nbOccupé == taille) { + msg = msg + " (PLEIN)"; + } else { + msg = msg + " (" + nbOccupé + ")"; + } + msg = msg + (char) 27 + "[0m"; + System.out.println(msg); + + TAS.occupied.set(false); + + notifyAll(); + System.out.println("notification"); + + } // déposer() + + synchronized public int retirer() { // Item remove() + int i; // Item i + + while (nbOccupé <= 0 && !occupied.compareAndSet(false, true)) { + try { + System.out.println("wait conso"); + this.wait(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + // retrait + i = tampon[tête]; + tête = (tête + 1) % taille; + nbOccupé--; + + // affichage pour le test uniquement + String msg = (char) 27 + "[0;31m" + "C : " + i; + if (nbOccupé == 0) { + msg = msg + " (VIDE)"; + } else { + msg = msg + " (" + nbOccupé + ")"; + } + msg = msg + (char) 27 + "[0m"; + System.out.println(msg); + + TAS.occupied.set(false); + + notifyAll(); + System.out.println("notification"); + + return i; + } // retirer +} // TamponBorné + +/* ---------- inutile de modifier ce qui suit ---------- */ + +class Producteur implements Runnable { + private TamponBorné tampon; + + public Producteur(TamponBorné t) { + tampon = t; + } + + public void run() { + try { + Thread.sleep(10); // pour le test : initialement, les consommateurs trouveront tous un tampon vide + for (int i = 0; i < 25; i++) { + // possible de trouver des producteurs bloqués à la fin, selon le nb de + // consommateurs + tampon.déposer(); + Thread.sleep(2 * i); // producteurs ralentissent un peu + } + } catch (InterruptedException e) { + System.out.println("interrompu"); + } + } +} + +class Consommateur implements Runnable { + private TamponBorné tampon; + private int identité; + + public Consommateur(TamponBorné t) { + tampon = t; + } + + public void run() { + int res; + for (int i = 0; i < 25; i++) { + // possible de trouver des consommateurs bloqués à la fin, selon le nb de + // producteurs + res = tampon.retirer(); + try { + Thread.sleep(10 * i); // consommateurs ralentissent davantage + } catch (InterruptedException e) { + System.out.println("interrompu"); + } + } + } +} + +public class ProdConso { + public static void main(String[] args) { + int nbProd = 5; + int nbConso = 10; + int tailleTampon = 10; + // aucun blindage : on suppose que les valeurs de paramètres fournies sont + // raisonnables + if (args.length != 3) { + System.out.println("java ProdConso "); + System.out.println("-> choix par défaut : " + nbProd + "/" + nbConso + "/" + tailleTampon); + } else { + nbProd = Integer.parseInt(args[0]); + nbConso = Integer.parseInt(args[1]); + tailleTampon = Integer.parseInt(args[2]); + } + System.out.println("nbProd (arg1) : " + nbProd + " /nbConso (arg2) : " + nbConso + " /nbCases) (arg3) : " + + tailleTampon + "\n"); + TamponBorné t = new TamponBorné(tailleTampon); + for (int i = 0; i < nbProd; i++) { + new Thread(new Producteur(t)).start(); + } + for (int i = 0; i < nbConso; i++) { + new Thread(new Consommateur(t)).start(); + } + // ajouter éventuellement un thread pour gérer l'arrêt et une prise de cliché + // finale + } +} \ No newline at end of file diff --git a/TP2/Mutex/TAS.java b/TP2/Mutex/TAS.java new file mode 100644 index 0000000..7dce13a --- /dev/null +++ b/TP2/Mutex/TAS.java @@ -0,0 +1,43 @@ +import java.util.concurrent.atomic.AtomicBoolean; + +public class TAS { + + static AtomicBoolean occupied = new AtomicBoolean(false); + + public static void main(String[] args) { + Thread t1 = new Thread(new Proc2(), "1"); + Thread t0 = new Thread(new Proc2(), "0"); + t1.start(); + t0.start(); + } +} + +class Proc2 implements Runnable { + int id; // identifiant du Thread + + public void run() { + id = Integer.parseInt(Thread.currentThread().getName()); + + while (true) { // SC = Section Critique + System.out.println("Thread " + id + " est hors SC"); + entrer(); + System.out.println("Thread " + id + " est en SC"); + sortir(); + } + } + + void entrer() { + System.out.println("Thread " + id + " veut entrer en SC "); + while (!TAS.occupied.compareAndSet(false, true)) { + System.out.println( + (char) 27 + "[0;90m" + "Thread " + id + " attend que TAS ne soit plus occupé" + (char) 27 + "[0m"); + } + System.out.println("Thread " + id + " est entré en SC "); + } + + void sortir() { + System.out.println("Thread " + id + " sort de SC "); + TAS.occupied.set(false); + System.out.println("Thread " + id + " est sortit de SC "); + } +} \ No newline at end of file diff --git a/TP2/Mutex/reponses.md b/TP2/Mutex/reponses.md new file mode 100644 index 0000000..b91715c --- /dev/null +++ b/TP2/Mutex/reponses.md @@ -0,0 +1,5 @@ +1. On rajoute les keywords synchronized aux méthodes qui effectuent des lectures et des écritures sur Peterson (pour rendre ces méthodes atomiques). + +2. Non, puisque les opérations de lectures et d'écritures (entrer et sortir) sont atomiques (grâce à synchronized en java), l'ordre des modifications de Peterson.demande et Peterson.tour importe peu. + +3. diff --git a/TP3/EtatFourchette.java b/TP3/EtatFourchette.java new file mode 100644 index 0000000..b5692cd --- /dev/null +++ b/TP3/EtatFourchette.java @@ -0,0 +1,7 @@ +// Time-stamp: <24 fév 2010 09:39 queinnec@enseeiht.fr> + +public enum EtatFourchette { + Table, + AssietteGauche, + AssietteDroite; +} diff --git a/TP3/EtatPhilosophe.java b/TP3/EtatPhilosophe.java new file mode 100644 index 0000000..a88fa5a --- /dev/null +++ b/TP3/EtatPhilosophe.java @@ -0,0 +1,8 @@ +// Time-stamp: <24 fév 2010 09:39 queinnec@enseeiht.fr> + +public enum EtatPhilosophe { + Pense, + Demande, + Mange; +} + diff --git a/TP3/IHMArgs.java b/TP3/IHMArgs.java new file mode 100644 index 0000000..3b067db --- /dev/null +++ b/TP3/IHMArgs.java @@ -0,0 +1,126 @@ +// Time-stamp: <03 mai 2013 11:13 queinnec@enseeiht.fr> + +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import java.io.File; +import java.util.List; +import java.util.LinkedList; + +public class IHMArgs extends JDialog { + + private int nbPhilo = 2; + private int implantation = 0; + + public IHMArgs (Frame frame) + { + super(frame,"Arguments",true); + setLocationRelativeTo(frame); + + // Listener Fermeture du dialogue + addWindowListener(new WindowAdapter() { + public void windowClosing(WindowEvent e) + { + System.exit(0); + } + }); + + /* ===== choix de l'implantation ===== */ + final String[] choix = trouver_implantations("StrategiePhilo"); + JPanel jp_implantation = new JPanel(); + JComboBox jComboBox = new JComboBox(choix); + jComboBox.setSelectedIndex(implantation); + jComboBox.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + JComboBox source = (JComboBox) e.getSource(); + implantation = source.getSelectedIndex(); + } + }); + jp_implantation.add(jComboBox); + + /* ===== Nombre de Philosophes ===== */ + final IHMChoixNombre jp_nbPhi = new IHMChoixNombre(2,30,4,null); + + /* ===== Boutons ===== */ + JPanel jp_boutons = new JPanel(new GridLayout(1,0,5,10)); + // OK + JButton jb_ok = new JButton("OK"); + jb_ok.addActionListener(new ActionListener() + { + public void actionPerformed(ActionEvent evt) + { + nbPhilo = jp_nbPhi.getValeur(); + setVisible(false); + Main.initialiser (choix[implantation], nbPhilo); + } + }); + jp_boutons.add(jb_ok); + // Annuler + JButton jb_annuler = new JButton("Annuler"); + jb_annuler.addActionListener(new ActionListener() + { + public void actionPerformed(ActionEvent evt) + { + System.exit (0); + } + }); + jp_boutons.add(jb_annuler); + /* ==== Assemblage ==== */ + Container contentPane = getContentPane(); + contentPane.add(new JLabel(" Implantation : ")); + contentPane.add(jp_implantation); + contentPane.add(new JLabel(" Philosophes : ")); + contentPane.add(jp_nbPhi); + contentPane.add(jp_boutons); + /* ==== Disposition ==== */ + GridBagLayout gridbag = new GridBagLayout(); + getContentPane().setLayout(gridbag); + // Contraintes + GridBagConstraints c = new GridBagConstraints(); + c.fill = GridBagConstraints.BOTH; + c.gridwidth = GridBagConstraints.REMAINDER; + gridbag.setConstraints(jp_implantation, c); + gridbag.setConstraints(jp_nbPhi, c); + gridbag.setConstraints(jp_boutons, c); + + pack(); + setVisible(true); + } + + private String[] trouver_implantations (String interfaceName) + { + List lesChoix = new LinkedList(); + // Récupére les noms de fichier + String[] files = (new File(".")).list(); + // L'interface que les classes doivent implanter + Class interf = null; + try { + interf = Class.forName (interfaceName); + } catch (ClassNotFoundException e) { + System.err.println ("Panic: ne trouve pas l'interface "+interfaceName+" :"+e); + System.exit (1); + } + // Vérifions qu'ils implantent la bonne interface + for (int i = 0; i < files.length; i++) { + Class implant; + if (files[i].endsWith (".class")) { + String classname = files[i].substring (0, files[i].length()-6); + try { + implant = Class.forName (classname); + } catch (ClassNotFoundException e) { + implant = null; + } + if ((implant != null) && (! classname.equals(interfaceName)) && interf.isAssignableFrom (implant)) { + // ok ! + lesChoix.add (classname); + } + } + } + // Y a-t-il au moins une classe ? + if (lesChoix.isEmpty()) { + System.out.println ("Aucune implantation de "+interfaceName+" trouvee !"); + System.exit (1); + } + return lesChoix.toArray (new String[0]); + } +} diff --git a/TP3/IHMChoixNombre.java b/TP3/IHMChoixNombre.java new file mode 100644 index 0000000..8ca4379 --- /dev/null +++ b/TP3/IHMChoixNombre.java @@ -0,0 +1,93 @@ +// Time-stamp: <02 mai 2013 11:59 queinnec@enseeiht.fr> + +import javax.swing.*; +import javax.swing.event.*; +import java.awt.event.*; +import java.util.Hashtable; + +@SuppressWarnings("serial") +public class IHMChoixNombre extends JPanel implements ActionListener { + + private JTextField textField; + private JSlider js; + private int valeur; + private int min, max; + + public IHMChoixNombre(int min, int max, int ini, + ChangeListener otherChangeListener) + { + super(); + this.min = min; + this.max = max; + valeur = ini; + textField = new JTextField(Integer.toString(ini), 3); + textField.addActionListener(this); + textField.addFocusListener(new FocusAdapter() { + public void focusLost (FocusEvent e) { + actionPerformed (null); + } + }); + this.add(textField); + + js = new JSlider (JSlider.HORIZONTAL, min, max, ini); + js.addChangeListener(new ChangeListener() { + public void stateChanged(ChangeEvent e) { + JSlider source = (JSlider) e.getSource(); + valeur=source.getValue(); + textField.setText(Integer.toString(valeur)); + } + }); + if (otherChangeListener != null) + js.addChangeListener(otherChangeListener); + + js.setMajorTickSpacing(10); + js.setMinorTickSpacing(5); + js.setPaintTicks(true); + // crée table de labels + Hashtable labelTable = new Hashtable(); + labelTable.put( new Integer(min), new JLabel(Integer.toString(min)) ); + labelTable.put( new Integer(max), new JLabel(Integer.toString(max)) ); + js.setLabelTable (labelTable); + js.setPaintLabels (true); + + this.add(js); + } + + public void setEnabled(boolean bool) + { + textField.setEnabled(bool); + js.setEnabled(bool); + } + + public void actionPerformed(ActionEvent e) + { + try { + valeur = Integer.parseInt(textField.getText()); + if (valeur < min) { + textField.setText(Integer.toString(min)); + valeur = min; + } + else if (valeur > max) { + textField.setText(Integer.toString(max)); + valeur = max; + } + js.setValue(valeur); + } + catch (NumberFormatException exc) { + textField.setText(Integer.toString(min)); + valeur = min; + } + } + + public int getValeur () + { + return valeur; + } + + public void setInitialValue (int ini) + { + textField.setText(Integer.toString(ini)); + js.setValue(ini); + } + +} diff --git a/TP3/IHMParametres.java b/TP3/IHMParametres.java new file mode 100644 index 0000000..f7ffd8b --- /dev/null +++ b/TP3/IHMParametres.java @@ -0,0 +1,88 @@ +// Time-stamp: <03 mai 2013 11:13 queinnec@enseeiht.fr> + +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; + +/** Interface de gestion des paramètres de la simulation. */ +public class IHMParametres extends JDialog { + + // fréquence actuellement en action + private int freq = 2; + // nouvelle fréquence en cours de choix, avant confirmation + // (ok ou appliquer) + private int freqCourante = 2; + + private final static String[] choix + = { "Peu gourmands", "Assez gourmands", "Fréquents", + "Très gourmands", "Extrêmement gourmands"}; + + public IHMParametres (Frame frame) + { + super(frame,"Paramètres de la simulation",false); + setLocationRelativeTo(frame); + + /* ===== Fréquence des demandes ===== */ + JPanel jp_freq = new JPanel(); + jp_freq.add(new JLabel(" Fréquence des demandes : ")); + final JComboBox jc_freq = new JComboBox(choix); + jc_freq.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + JComboBox source = (JComboBox) e.getSource(); + freqCourante = source.getSelectedIndex(); + } + }); + jp_freq.add(jc_freq); + + this.addComponentListener (new ComponentAdapter() { + public void componentShown (ComponentEvent e) { + + jc_freq.setSelectedIndex (freq); + freqCourante = freq; + }}); + + + /* ===== Boutons ===== */ + JPanel jp_boutons = new JPanel(new GridLayout(0,3,5,10)); + // OK + JButton jb_ok = new JButton("OK"); + jb_ok.addActionListener(new ActionListener() + { + public void actionPerformed(ActionEvent evt) + { + freq = freqCourante; + Main.setSleepDuration(freq); + setVisible(false); + } + }); + jp_boutons.add(jb_ok); + // Appliquer + JButton jb_appli = new JButton("Appliquer"); + jb_appli.addActionListener(new ActionListener() + { + public void actionPerformed(ActionEvent evt) + { + freq = freqCourante; + Main.setSleepDuration (freq); + } + }); + jp_boutons.add(jb_appli); + // Annuler + JButton jb_annuler = new JButton("Annuler"); + jb_annuler.addActionListener(new ActionListener() + { + public void actionPerformed(ActionEvent evt) + { + setVisible(false); + } + }); + jp_boutons.add(jb_annuler); + /* ==== Assemblage ==== */ + Container contentPane = getContentPane(); + contentPane.add(jp_freq,BorderLayout.NORTH); + contentPane.add(jp_boutons,BorderLayout.SOUTH); + pack(); + setSize(500,200); + } + +} diff --git a/TP3/IHMPhilo.java b/TP3/IHMPhilo.java new file mode 100644 index 0000000..f3f8209 --- /dev/null +++ b/TP3/IHMPhilo.java @@ -0,0 +1,466 @@ +// Time-stamp: <10 jui 2013 16:51 queinnec@enseeiht.fr> +// V0.1 9/13 (PM, commentaires) +// V0.2 10/14 (PM, aide) +// V0.3 10/15 (PM, correction placement fourchettes) + +import javax.swing.*; +import javax.swing.event.*; +import java.awt.*; +import java.awt.event.*; +import java.util.Hashtable; +import Synchro.Simulateur; + +/** La partie graphique des philosophes. + * Les seules méthodes visibles sont IHMPhilo.changerEtat et IHMPhilo.poser. + * + * Note : le code est crade et mériterait une réécriture complète. + */ +public class IHMPhilo { + + /* Attention: les variables d'état partagées de l'affichage + * (lesFourchettes[].etat et lesPhilos[].etat) ne sont pas protégées. + * Il ne doit pas y avoir de problème avec des appels concurrents entre + * poser et/ou changer_etat (a priori, ils concernent des objets + * différents). Il ne devrait pas y avoir de surprise lors d'une + * exécution concurrente avec repaint() car cette fonction est + * thread-safe. + */ + + private final static boolean verbose = false; + private static int nbPhilosophes; + + private Simulateur simu; + + class Point { + public int x = 0; + public int y = 0; + /** Changement de position, coordonnées cartésiennes. */ + public void setPosition (int cardx, int cardy) { + x = cardx; + y = cardy; + } + /** Changement de position, coordonnées angulaires. */ + public void setPosition (double rayon, double unAngle) + { + x = centreTable.x + (int) (rayon * Math.cos (unAngle)); + y = centreTable.y + (int) (rayon * Math.sin (unAngle)); + } + } + + /* Le rayon d'une assiette et de la table. */ + private double rayonAss, rayonTable; + + /* Le centre de la table. */ + private Point centreTable = new Point(); + + /* Les 3 placements d'une fourchette, selon son état. */ + class Fourchette { + EtatFourchette etat; + Point table[]; + Point assGau[]; + Point assDro[]; + public Fourchette () { + etat = EtatFourchette.Table; + table = new Point[2]; + assGau = new Point[2]; + assDro = new Point[2]; + for(int i = 0; i < 2; i++) { + table[i] = new Point(); + assGau[i] = new Point(); + assDro[i] = new Point(); + } + + } + } + + /* Le placement d'un philosophe, et son état à l'écran. */ + class Philosophe { + int no; + Point position; + EtatPhilosophe etat; + public Philosophe (int i) { + no = i; + position = new Point(); + etat = EtatPhilosophe.Pense; + } + } + + /* Le centre d'une assiette. */ + class Assiette { + Point position; + public Assiette () { + position = new Point(); + } + } + + private static Philosophe[] lesPhilos; // tableau [nbPhilosophes] + private static Fourchette[] lesFourchettes; // tableau [nbPhilosophes] + private Assiette[] lesAssiettes; // tableau [nbPhilosophes] + + + /****************************************************************/ + + static double distance (int x1, int y1, int x2, int y2) + { + return Math.sqrt((double) (x1-x2)*(x1-x2) + (y1-y2)*(y1-y2)); + } + + /****************************************************************/ + class FenetreDessin extends JPanel { + + /* La police utilisée pour les numéros des philosophes. */ + private final Font font = new Font("Helvetica", Font.BOLD, 14); // def = ("Times",Font.PLAIN,12); + private final FontMetrics fontMetrics = getFontMetrics(font); + + FenetreDessin () + { + super(); + setFont (font); + } + + /* appelé lors de la création et le redimensionnement de la fenêtre */ + void computePlacement () + { + int taille_fenetre_x, taille_fenetre_y, taille_fenetre; + double unAngle, unAngleFour, deplAngle; + double distTableAss, distTablePhilo; + double centreAss, centrePhilo; + double posFour[] = new double[2]; + + taille_fenetre_x = getWidth(); + taille_fenetre_y = getHeight(); + taille_fenetre = (taille_fenetre_x < taille_fenetre_y ? + taille_fenetre_x : taille_fenetre_y); + + centreTable.setPosition(taille_fenetre_x / 2, taille_fenetre_y / 2); + rayonTable = taille_fenetre * 6.0 / (8.0 * 2.0); + distTableAss = taille_fenetre / 15.0; + distTablePhilo = taille_fenetre / 20.0; + centreAss = rayonTable - distTableAss; + centrePhilo = rayonTable + distTablePhilo; + + unAngle = 0.0; + unAngleFour = 0.0; + deplAngle = (2.0 * Math.PI) / nbPhilosophes; + rayonAss = (rayonTable - centreAss) / 2.0; + + posFour[0] = centreAss + rayonAss; + posFour[1] = centreAss - rayonAss; + + for (int i = 0; i < nbPhilosophes; i++) { + + lesPhilos[i].position.setPosition (centrePhilo, unAngle); + lesAssiettes[i].position.setPosition (centreAss, unAngle); + + unAngleFour = unAngle - deplAngle / 2.0; + lesFourchettes[i].table[0].setPosition (posFour[0], unAngleFour); + lesFourchettes[i].table[1].setPosition (posFour[1], unAngleFour); + + unAngleFour = unAngle - deplAngle + deplAngle / 12.0; + lesFourchettes[i].assDro[0].setPosition (posFour[0], unAngleFour); + lesFourchettes[i].assDro[1].setPosition (posFour[1], unAngleFour); + + unAngleFour = unAngle - deplAngle / 12.0; + lesFourchettes[i].assGau[0].setPosition (posFour[0], unAngleFour); + lesFourchettes[i].assGau[1].setPosition (posFour[1], unAngleFour); + + unAngle = unAngle + deplAngle; + + } + repaint(); + } + + void tracerCercle (Graphics g, boolean fill, Point unPt, int rayon) + { + if (fill) + g.fillOval(unPt.x - rayon, unPt.y - rayon, rayon*2, rayon*2); + else + g.drawOval(unPt.x - rayon, unPt.y - rayon, rayon*2, rayon*2); + } + + + void tracerFourchette (Graphics g, Point pos[]) + { + g.drawLine (pos[0].x, pos[0].y, pos[1].x, pos[1].y); + } + + void tracerNumero (Graphics g, int num, Point pos) + { + String str = Integer.toString(num); + g.drawString (str, pos.x - fontMetrics.stringWidth(str) / 2, + (int) (pos.y + fontMetrics.getLineMetrics(str,g).getHeight() / 2)); + } + + public void paintComponent (Graphics g) + { + super.paintComponent(g); + g.setColor (Color.black); + tracerCercle (g, false, centreTable,(int) rayonTable); + for (int i = 0; i < nbPhilosophes; i++) { + tracerCercle (g, false, lesAssiettes[i].position,(int) rayonAss); + tracerNumero (g, i, lesAssiettes[i].position); + + EtatFourchette etat = lesFourchettes[i].etat; + if (etat == EtatFourchette.Table) + tracerFourchette (g, lesFourchettes[i].table); + else if (etat == EtatFourchette.AssietteDroite) + tracerFourchette (g, lesFourchettes[i].assDro); + else if (etat == EtatFourchette.AssietteGauche) + tracerFourchette (g, lesFourchettes[i].assGau); + + if (isInvalidState(i)) + g.setColor(Color.red); + if (lesPhilos[i].etat == EtatPhilosophe.Mange) + tracerCercle (g, true, lesPhilos[i].position, 7); + else if (lesPhilos[i].etat != EtatPhilosophe.Pense) + tracerCercle (g, false, lesPhilos[i].position, 7); + g.setColor(Color.black); + } + } + } // class FenetreDessin + + /* Le panel où l'on dessine les philosophes. */ + private static FenetreDessin jp_fen; + + /****************************************************************/ + private static void afficher () + { + jp_fen.repaint(); + } + + /****************************************************************/ + + /* Renvoie vrai si le philosophe i mange et l'un de ses voisins mange aussi. */ + private boolean isInvalidState(int i) + { + return ((lesPhilos[i].etat == EtatPhilosophe.Mange) + && ((lesPhilos[Main.PhiloGauche(i)].etat == EtatPhilosophe.Mange) || lesPhilos[Main.PhiloDroite(i)].etat == EtatPhilosophe.Mange)); + } + + /** Indique à l'affichage que la fourchette noFourch passe dans l'état spécifié (assiette de droite, assiette de gauche, table). */ + public static void poser (int noFourch, EtatFourchette pos) + { + lesFourchettes[noFourch].etat = pos; + if (verbose) + System.out.println("Fourchette "+noFourch+" posée "+pos); + afficher(); + } + + /** Indique à l'affichage que le philosophe noPhilo passe dans l'état spécifié (penseur, demandeur, mangeur). */ + public static void changerEtat (int noPhilo, EtatPhilosophe etat) + { + lesPhilos[noPhilo].etat = etat; + if (verbose) + System.out.println("Philosophe "+noPhilo+" change d'état : "+etat); + afficher(); + } + + /****************************************************************/ + + /* Initialise la fenêtre d'affichage + */ + public IHMPhilo (String nomstrategie, int _nbPhilosophes, Simulateur _simu) + { + simu = _simu; + nbPhilosophes = _nbPhilosophes; + lesPhilos = new Philosophe[nbPhilosophes]; + lesFourchettes = new Fourchette[nbPhilosophes]; + lesAssiettes = new Assiette[nbPhilosophes]; + + for (int i = 0; i < nbPhilosophes; i++) { + lesPhilos[i] = new Philosophe(i); + lesFourchettes[i] = new Fourchette(); + lesAssiettes[i] = new Assiette(); + } + + // Fenetre + final JFrame fenetre = new JFrame("Philosophes"); + // Listener Fermeture de la fenetre + fenetre.addWindowListener(new WindowAdapter() + { + public void windowClosing(WindowEvent e) + { + // Fermeture de la fenetre graphique + System.exit(0); + } + }); + // Listener Redimensionnement de la fenetre + fenetre.addComponentListener(new ComponentAdapter() + { + public void componentResized(ComponentEvent e) + { + jp_fen.computePlacement (); + } + }); + // Listener touche 'q' + fenetre.addKeyListener (new KeyAdapter() + { + public void keyTyped (KeyEvent e) + { + if (e.getKeyChar() == 'q') { + fenetre.dispatchEvent (new WindowEvent(fenetre,WindowEvent.WINDOW_CLOSING)); + //System.exit (0); + } + } + }); + + /* ===== boutons ===== */ + JPanel jp_boutons = new JPanel(new GridLayout(0,4,5,10)); + // Quitter + JButton jb_quitter = new JButton("Quitter"); + jb_quitter.addActionListener(new ActionListener() + { + public void actionPerformed(ActionEvent evt) + { + System.exit(0); + } + }); + jp_boutons.add(jb_quitter); + // Parametres + final JDialog dialogParam = new IHMParametres (fenetre); + JButton jb_parametres = new JButton("Paramètres"); + jb_parametres.setToolTipText("Paramétrage fin du comportement"); + jb_parametres.addActionListener(new ActionListener() + { + public void actionPerformed(ActionEvent evt) + { + dialogParam.setVisible (true); + } + }); + jp_boutons.add(jb_parametres); + // Pause + final JButton jb_pause = new JButton("Pause"); + jb_pause.setToolTipText("Suspension/reprise du temps"); + /* Event clicked on button "pause". */ + jb_pause.addActionListener(new ActionListener() + { + public void actionPerformed(ActionEvent evt) + { + simu.swapRunning(); + if (simu.getRunning()) + jb_pause.setText("Pause"); + else + jb_pause.setText("Temps suspendu"); + } + }); + jp_boutons.add(jb_pause); + // Aide + JEditorPane jep_aide = + new JEditorPane("text/html", + "
"+ + "Les Philosophes et les Spaghettis"+ + "
------------------------

"+ + "Problème : un philosophe ne peut manger des spaghettis qu'avec deux fourchettes. Il doit donc utiliser les deux fourchettes adjacentes à son assiette pour pouvoir manger. Ce faisant, il empêche ses deux voisins de manger.

"+ + "Symboles :"+ + "
    "+ + "
  • un disque plein indique un philosophe mangeant ;"+ + "
      "+ + "
    • un disque noir indique une situation normale ;"+ + "
    • un disque rouge signale une incohérence possible ;"+ + "
    "+ + "
  • un cercle indique un philosophe demandeur ;"+ + "
  • aucun symbole indique un philosophe penseur ;"+ + "
"+ + "Actions :"+ + "
    "+ + "
  • en cliquant dans l'assiette d'un philosophe, vous pouvez forcer une transition: penser -> demander, ou manger -> penser (la transition demander -> manger reste du ressort de la synchronisation);"+ + "
  • pause permet de suspendre le temps de la simulation. Les actions forcées sont par contre toujours possibles;"+ + "
  • vous pouvez régler la vitesse de la simulation avec l'échelle du bas."+ + "
"+ + ""); + jep_aide.setEditable (false); + JOptionPane pane = new JOptionPane(new JScrollPane (jep_aide)); + final JDialog dialogAide = pane.createDialog(fenetre,"Aide"); + dialogAide.setModal (false); + dialogAide.setSize(500,500); + JButton jb_aide = new JButton(" Aide "); + jb_aide.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent evt) + { + dialogAide.setVisible(true); + }}); + jp_boutons.add(jb_aide); + /* ===== Le nom de la stratégie ===== */ + JPanel jp_strategie = new JPanel(); + jp_strategie.add(new JLabel(nomstrategie)); + jp_strategie.setBorder(BorderFactory.createEtchedBorder()); + + /* ===== Fenêtre de dessin ===== */ + jp_fen = new FenetreDessin(); + /* Event button release on drawingarea. */ + jp_fen.addMouseListener(new MouseAdapter() + { + public void mouseClicked(MouseEvent e) + { + int mx, my; + mx = e.getX(); + my = e.getY(); + /* Les assiettes */ + for (int i = 0; i < nbPhilosophes; i++) { + if (distance (mx, my, lesAssiettes[i].position.x, + lesAssiettes[i].position.y) <= rayonAss) + simu.wakeup (i); + } + } + }); + + + /* ===== Le réglage de vitesse du temps ===== */ + JPanel jp_vitesse = new JPanel(); + jp_vitesse.setToolTipText("Vitesse d'écoulement du temps simulé"); + jp_vitesse.setBorder(BorderFactory.createEtchedBorder()); + jp_vitesse.add(new JLabel(" Vitesse du temps : ")); + JSlider js_vitesseTemps = new JSlider(JSlider.HORIZONTAL,1,100,1); + // Event "value_changed" de l'ajustement de l'échelle de vitesse du temps. + js_vitesseTemps.addChangeListener(new ChangeListener() { + public void stateChanged(ChangeEvent e) { + JSlider source = (JSlider) e.getSource(); + simu.setTimespeed(source.getValue()); + } + }); + js_vitesseTemps.setMajorTickSpacing(10); + js_vitesseTemps.setPaintTicks(true); + // crée table de labels + Hashtable labelTable = new Hashtable(); + labelTable.put( new Integer( 1 ), new JLabel("1") ); + labelTable.put( new Integer( 100 ), new JLabel("100") ); + js_vitesseTemps.setLabelTable( labelTable ); + js_vitesseTemps.setPaintLabels(true); + jp_vitesse.add(js_vitesseTemps); + + + /* ===== Assemblage ===== */ + Container contentPane = fenetre.getContentPane(); + contentPane.add(jp_boutons); + contentPane.add(jp_strategie); + contentPane.add(jp_fen); + contentPane.add(jp_vitesse); + /* ==== Contraintes ==== */ + GridBagLayout gridbag = new GridBagLayout(); + contentPane.setLayout(gridbag); + // Contraintes pour la fenêtre de dessin + GridBagConstraints c_fen = new GridBagConstraints(); + c_fen.fill = GridBagConstraints.BOTH; + c_fen.gridwidth = GridBagConstraints.REMAINDER; + c_fen.weightx = 1.0; + c_fen.weighty = 1.0; + gridbag.setConstraints(jp_fen, c_fen); + // Contraintes pour les autres éléments + GridBagConstraints c = new GridBagConstraints(); + c.fill = GridBagConstraints.BOTH; + c.gridwidth = GridBagConstraints.REMAINDER; + gridbag.setConstraints(jp_boutons, c); + gridbag.setConstraints(jp_strategie, c); + gridbag.setConstraints(jp_vitesse, c); + + //jp_fen.setDebugGraphicsOptions(DebugGraphics.LOG_OPTION); + + fenetre.pack(); + int taille_fen_x = 600; + int taille_fen_y = 500; + fenetre.setSize(taille_fen_x,taille_fen_y); + jp_fen.computePlacement (); + fenetre.setVisible(true); + } + +} diff --git a/TP3/Main.java b/TP3/Main.java new file mode 100644 index 0000000..5c23bb6 --- /dev/null +++ b/TP3/Main.java @@ -0,0 +1,180 @@ +// Time-stamp: <02 mai 2013 10:25 queinnec@enseeiht.fr> +// V0.1 10/15 (PM, correction gauche<->droite) + +import Synchro.Simulateur; + +public class Main { + + /* Initialisé depuis la ligne de commande */ + public static int nbPhilosophes; + + // Chaque philosophe mange entre MinDelayMange et MaxDelayMange secondes, + // et pense entre MinDelayPense et MaxDelayPense (les bornes incluses). + public final static int MinDelayMange = 5; + public final static int MaxDelayMange = 20; + public static int MinDelayPense; /* 5 */ + public static int MaxDelayPense; /* 20 */ + + private static Simulateur simu; + private static StrategiePhilo strategie; + + /* PhiloDroite est le numéro du philosophe à droite de no. */ + public static int PhiloGauche (int no) + { + if (no == nbPhilosophes - 1) + return 0; + else + return no + 1; + } + + /* PhiloGauche est le numéro du philosophe à gauche de no. */ + public static int PhiloDroite (int no) + { + if (no == 0) + return nbPhilosophes - 1; + + return no - 1; + } + + /* FourchetteDroite est le numéro de la fourchette de droite du philo no. */ + public static int FourchetteGauche (int no) + { + if (no == nbPhilosophes - 1) + return 0; + else + return no + 1; + } + + /* FourchetteGauche est le numéro de la fourchette de gauche du philo no. */ + public static int FourchetteDroite (int no) + { + return no; + } + + /****************************************************************/ + /* Utilisé par IHMPhilo. */ + public static void setSleepDuration (int intensite) + { + switch (intensite) { + case 0: /* Peu gourmands */ + MinDelayPense = 20 * MinDelayMange; + MaxDelayPense = 20 * MaxDelayMange; + break; + case 1: /* Assez gourmands */ + MinDelayPense = 5 * MinDelayMange; + MaxDelayPense = 5 * MaxDelayMange; + break; + case 2: /* gourmands (initial) */ + MinDelayPense = MinDelayMange; + MaxDelayPense = MaxDelayMange; + break; + case 3: /* Très gourmands */ + MinDelayPense = (MinDelayMange + 1) / 3; + MaxDelayPense = (MaxDelayMange + 1) / 3; + break; + case 4: /* Extrêmement gourmands */ + MinDelayPense = (MinDelayMange + 1) / 8; + MaxDelayPense = (MaxDelayMange + 1) / 8; + break; + default: + System.out.println ("Frequence de sommeil hors des bornes."); + } + //System.out.println ("Delai pense = "+MinDelayPense+"-"+MaxDelayPense+", utilise = "+MinDelayMange+"-"+MaxDelayMange); + } + + static public void initialiser (String nomImplantation, int nbPhilo) { + nbPhilosophes = nbPhilo; + setSleepDuration (2); + simu = new Simulateur (nbPhilosophes); + + strategie = charger_implantation("StrategiePhilo", nomImplantation, nbPhilosophes); + + new IHMPhilo (strategie.nom(), nbPhilosophes, simu); + + for (int i = 0; i < nbPhilosophes; i++) { + Runnable philo = new ProcessusPhilosophe (i, strategie, simu); + Thread t = new Thread(philo); + t.start(); + } + + } + + /** Crée un objet à partir de l'implantation implName, qui doit implanter + * l'interface interfName et dont le constructeur prendra un int en + * argument. */ + private static StrategiePhilo charger_implantation (String interfName, String implName, int nbPhilo) + { + StrategiePhilo res = null; + + // Obtenir l'interface interfName + Class interf; + try { + interf = Class.forName (interfName); + } catch (ClassNotFoundException e) { + System.err.println ("Panic: ne trouve pas l'interface "+interfName+" :"+e); + return null; + } + + // Trouve la classe implName (ou interfName_implName) + Class implant = null; + try { + implant = Class.forName (implName); + } catch (ClassNotFoundException e1) { + try { + implant = Class.forName (interfName+"_"+implName); + } catch (ClassNotFoundException e2) { + System.err.println ("Impossible de trouver la classe "+implName+": "+e1); + return null; + } + } + + // Vérifie qu'elle implante la bonne interface + if (! interf.isAssignableFrom (implant)) { + System.err.println ("La classe "+implant.getName()+" n'implante pas l'interface "+interf.getName()+"."); + return null; + } + + // Crée une instance + try { + Class[] consparam = { int.class }; + java.lang.reflect.Constructor cons = implant.getConstructor(consparam); + Object[] initargs = { new Integer(nbPhilo) }; + res = (StrategiePhilo) cons.newInstance (initargs); + } catch (NoSuchMethodException e) { + System.err.println ("Classe "+implant.getName()+": pas de constructeur adequat: "+e); + } catch (InstantiationException e) { + System.err.println ("Echec instation "+implant.getName()+": "+e); + } catch (IllegalAccessException e) { + System.err.println ("Echec instation "+implant.getName()+": "+e); + } catch (java.lang.reflect.InvocationTargetException e) { + System.err.println ("Echec instation "+implant.getName()+": "+e); + if (e.getCause() != null) { + System.err.println (" La cause est : " + e.getCause() + + " in " + (e.getCause().getStackTrace())[0]); + } + } catch (ClassCastException e5) { + System.err.println ("Echec instation "+implant.getName()+": n'est pas un "+interfName+": "+e5); + } + return res; + } + + public static void main (String args[]) + { + if ((args.length != 0) && (args.length != 2)) { + System.out.println("java Main "); + System.exit (1); + } + if (args.length == 0) { + new IHMArgs(null); + /* no return */ + } else { + String implantation = args[0]; + int nbPhilo = Integer.parseInt (args[1]); + if (nbPhilo < 2) { + System.out.println ("nb philo >= 2"); + System.exit (1); + } + initialiser (implantation, nbPhilo); + } + } +} diff --git a/TP3/PhiloSem.java b/TP3/PhiloSem.java new file mode 100644 index 0000000..f83b175 --- /dev/null +++ b/TP3/PhiloSem.java @@ -0,0 +1,66 @@ + +import java.util.concurrent.Semaphore; + +public class PhiloSem implements StrategiePhilo { + + /****************************************************************/ + + int nbPhilosophe; + int fg, fd; + + Semaphore[] fourchettes; + + public PhiloSem(int nbPhilosophes) { + this.nbPhilosophe = nbPhilosophes; + this.fourchettes = new Semaphore[Main.nbPhilosophes]; + for (int i = 0; i < fourchettes.length; i++) { + fourchettes[i] = new Semaphore(1, true); + } + } + + /** + * Le philosophe no demande les fourchettes. Précondition : il n'en possède + * aucune. Postcondition : quand cette méthode retourne, il possède les deux + * fourchettes adjacentes à son assiette. + */ + public void demanderFourchettes(int no) throws InterruptedException { + + int fg = Main.FourchetteGauche(no); + int fd = Main.FourchetteDroite(no); + + while (true) { + fourchettes[fd].acquire(); + IHMPhilo.poser(fd, EtatFourchette.AssietteGauche); + if (fourchettes[fg].tryAcquire()) { + IHMPhilo.poser(fg, EtatFourchette.AssietteDroite); + break; + } else { + fourchettes[fd].release(); + IHMPhilo.poser(fd, EtatFourchette.Table); + } + } + } + + /** + * Le philosophe no rend les fourchettes. Précondition : il possède les deux + * fourchettes adjacentes à son assiette. Postcondition : il n'en possède + * aucune. Les fourchettes peuvent être libres ou réattribuées à un autre + * philosophe. + */ + public void libererFourchettes(int no) { + int fd = Main.FourchetteDroite(no); + fourchettes[fd].release(); + IHMPhilo.poser(fd, EtatFourchette.Table); + + int fg = Main.FourchetteGauche(no); + fourchettes[fg].release(); + IHMPhilo.poser(fg, EtatFourchette.Table); + + } + + /** Nom de cette stratégie (pour la fenêtre d'affichage). */ + public String nom() { + return "Sémaphores, strat 1: fourchettes ressources critiques"; + } + +} diff --git a/TP3/PhiloSem2.java b/TP3/PhiloSem2.java new file mode 100644 index 0000000..1575ff3 --- /dev/null +++ b/TP3/PhiloSem2.java @@ -0,0 +1,63 @@ + +import java.util.concurrent.Semaphore; + +public class PhiloSem2 implements StrategiePhilo { + + /****************************************************************/ + + int nbPhilosophe; + int fg, fd; + + Semaphore[] fourchettes; + + public PhiloSem2(int nbPhilosophes) { + this.nbPhilosophe = nbPhilosophes; + this.fourchettes = new Semaphore[Main.nbPhilosophes]; + for (int i = 0; i < fourchettes.length; i++) { + fourchettes[i] = new Semaphore(1, true); + } + } + + /** + * Le philosophe no demande les fourchettes. Précondition : il n'en possède + * aucune. Postcondition : quand cette méthode retourne, il possède les deux + * fourchettes adjacentes à son assiette. + */ + public void demanderFourchettes(int no) throws InterruptedException { + + int fg = Main.FourchetteGauche(no); + int fd = Main.FourchetteDroite(no); + + fourchettes[fd].acquire(); + IHMPhilo.poser(fd, EtatFourchette.AssietteGauche); + + Thread.sleep(10000); + + fourchettes[fg].acquire(); + IHMPhilo.poser(fg, EtatFourchette.Table); + + } + + /** + * Le philosophe no rend les fourchettes. Précondition : il possède les deux + * fourchettes adjacentes à son assiette. Postcondition : il n'en possède + * aucune. Les fourchettes peuvent être libres ou réattribuées à un autre + * philosophe. + */ + public void libererFourchettes(int no) { + int fd = Main.FourchetteDroite(no); + fourchettes[fd].release(); + IHMPhilo.poser(fd, EtatFourchette.Table); + + int fg = Main.FourchetteGauche(no); + fourchettes[fg].release(); + IHMPhilo.poser(fg, EtatFourchette.Table); + + } + + /** Nom de cette stratégie (pour la fenêtre d'affichage). */ + public String nom() { + return "Sémaphores, strat 2: interblocage obligé"; + } + +} diff --git a/TP3/PhiloSem3.java b/TP3/PhiloSem3.java new file mode 100644 index 0000000..c1f0c07 --- /dev/null +++ b/TP3/PhiloSem3.java @@ -0,0 +1,83 @@ + +import java.util.concurrent.Semaphore; + +public class PhiloSem3 implements StrategiePhilo { + + /****************************************************************/ + + Semaphore mutex = new Semaphore(1); + EtatPhilosophe[] etats; + Semaphore[] bloqueur; + + boolean peutManger(int no) { + int pg = Main.PhiloGauche(no); + int pd = Main.PhiloDroite(no); + + return etats[pg] != EtatPhilosophe.Mange && etats[pd] != EtatPhilosophe.Mange; + } + + public PhiloSem3(int nbPhilosophes) throws InterruptedException { + this.bloqueur = new Semaphore[Main.nbPhilosophes]; + this.etats = new EtatPhilosophe[Main.nbPhilosophes]; + for (int i = 0; i < bloqueur.length; i++) { + bloqueur[i] = new Semaphore(0, true); + etats[i] = EtatPhilosophe.Pense; + } + } + + /** + * Le philosophe no demande les fourchettes. Précondition : il n'en possède + * aucune. Postcondition : quand cette méthode retourne, il possède les deux + * fourchettes adjacentes à son assiette. + */ + public void demanderFourchettes(int no) throws InterruptedException { + mutex.acquire(); + if (peutManger(no)) { + etats[no] = EtatPhilosophe.Mange; + mutex.release(); + } else { + etats[no] = EtatPhilosophe.Demande; + mutex.release(); + bloqueur[no].acquire(); + } + + int fg = Main.FourchetteGauche(no); + int fd = Main.FourchetteDroite(no); + IHMPhilo.poser(fg, EtatFourchette.AssietteDroite); + IHMPhilo.poser(fd, EtatFourchette.AssietteGauche); + } + + /** + * Le philosophe no rend les fourchettes. Précondition : il possède les deux + * fourchettes adjacentes à son assiette. Postcondition : il n'en possède + * aucune. Les fourchettes peuvent être libres ou réattribuées à un autre + * philosophe. + */ + public void libererFourchettes(int no) throws InterruptedException { + int pg = Main.PhiloGauche(no); + int pd = Main.PhiloDroite(no); + + mutex.acquire(); + etats[no] = EtatPhilosophe.Pense; + if (peutManger(pg) && etats[pg] == EtatPhilosophe.Demande) { + etats[pg] = EtatPhilosophe.Mange; + bloqueur[pg].release(); + } + if (peutManger(pd) && etats[pd] == EtatPhilosophe.Demande) { + etats[pd] = EtatPhilosophe.Mange; + bloqueur[pd].release(); + } + mutex.release(); + + int fg = Main.FourchetteGauche(no); + int fd = Main.FourchetteDroite(no); + IHMPhilo.poser(fg, EtatFourchette.Table); + IHMPhilo.poser(fd, EtatFourchette.Table); + } + + /** Nom de cette stratégie (pour la fenêtre d'affichage). */ + public String nom() { + return "Sémaphores, strat 3: scrute voisin"; + } + +} diff --git a/TP3/ProcessusPhilosophe.java b/TP3/ProcessusPhilosophe.java new file mode 100644 index 0000000..ccb6b84 --- /dev/null +++ b/TP3/ProcessusPhilosophe.java @@ -0,0 +1,48 @@ +// Time-stamp: v0.0 <28 Oct 2008 17:06 queinnec@enseeiht.fr> +// V0.1 6/9/13 (PM, correction de commentaires) + +import Synchro.Simulateur; + +/** Code d'un philosophe. */ +public class ProcessusPhilosophe implements Runnable +{ + /** Identifiant du philosophe. */ + private int no; + /** Quelle stratégie le philosophe utilise-t-il pour manipuler les fourchettes ? */ + private StrategiePhilo strategie; + /** Le simulateur d'avancement du temps. */ + private Simulateur simu; + + /** Constructeur. */ + public ProcessusPhilosophe (int no, StrategiePhilo strategie, Simulateur simu) { + this.no = no; + this.strategie = strategie; + this.simu = simu; + } + + /** Code d'un philosophe. */ + public void run() { + try { + simu.sleep (no, 0, Main.MaxDelayPense/2); + for (;;) { + // Demande + IHMPhilo.changerEtat (no, EtatPhilosophe.Demande); + strategie.demanderFourchettes (no); + + // Mange + IHMPhilo.changerEtat (no, EtatPhilosophe.Mange); + simu.sleep (no, Main.MinDelayMange, Main.MaxDelayMange); + + strategie.libererFourchettes (no); + + // Pense + IHMPhilo.changerEtat (no, EtatPhilosophe.Pense); + simu.sleep (no, Main.MinDelayPense, Main.MaxDelayPense); + } + } catch (InterruptedException e) { + // ok, die + throw new RuntimeException(e); + } + } +} + diff --git a/TP3/README.html b/TP3/README.html new file mode 100644 index 0000000..506b02c --- /dev/null +++ b/TP3/README.html @@ -0,0 +1,82 @@ + + + + + + + + + + +

Problème des philosophes

+

Énoncé

+

N philosophes sont autour d'une table. Il y a une assiette par philosophe, et une fourchette entre chaque assiette. Pour manger, un philosophe doit utiliser les deux fourchettes adjacentes à son assiette (et celles-là seulement).

+

Un philosophe peut être dans l'état :

+
    +
  • penseur : il n'utilise pas de fourchettes ;
  • +
  • mangeur : il utilise les deux fourchettes adjacentes ; aucun de ses voisins ne peut manger ;
  • +
  • demandeur : il souhaite manger mais ne dispose pas des deux fourchettes.
  • +
+

Visuellement les états mangeur/demandeur/penseur sont représentés par un rond noir (ou rouge en cas de possible problème)/un rond blanc/rien.

+

Code fourni

+
    +
  • StrategiePhilo.java : interface de la synchronisation entre philosophes.
  • +
  • PhiloSem.java : une implantation de cette interface.
  • +
  • ProcessusPhilosophe.java : code d'un philosophe.
  • +
  • Main.java : programme principal. Définit aussi les PhiloDroite(i), PhiloGauche(i), FourchetteGauche(i), FourchetteDroite(i).
  • +
  • EtatFourchette.java : définition des constantes pour fourchette placée sur la table, l'assiette gauche, l'assiette droite.
  • +
  • EtatPhilosophe.java : définition des constantes pour philosophe penseur, demandeur ou mangeur.
  • +
  • IHM*.java : interface utilisateur.
  • +
  • Synchro/Simulateur.java : le simulateur de temps.

  • +
  • Compilation:
    +javac *.java Synchro/*.java

  • +
  • Exécution:
    +java Main
    +java Main PhiloSem 10
    +(classe implantant l'interface StrategiePhilo) (nb de philosophes)

    +

    Le bouton d'aide de la fenêtre affichée par l'application en présente les fonctionnalités.

  • +
+

La classe Semaphore

+

La plateforme Java fournit la classe java.util.concurrent.Semaphore qui propose une implantation des sémaphores généraux, avec notamment :

+
    +
  • un constructeur prenant un paramètre entier, correspondant à la valeur initiale du sémaphore. Un second paramètre optionnel booléen, qui permet de préciser si le sémaphore créé est FIFO. Par défaut, les sémaphores de la classe java.util.concurrent.Semaphore ne sont pas FIFO.
    +Par exemple : s=new Semaphore(5,true) crée un sémaphore FIFO de valeur initiale 5.

  • +
  • une méthode acquire(), qui correspond à l'opération P()
  • +
  • une méthode release(), qui correspond à l'opération V()

  • +
+

Cette classe fournit en outre différentes méthodes destinées à faciliter l'écriture des programmes (au risque d'altérer la clarté et la sûreté de la conception). Il est ainsi possible d'effectuer des P()non bloquants (tryAcquire()), de demander à augmenter ou diminuer la valeur du sémaphore de plusieurs unités en une seule opération (acquire(k), release(j)), de consulter et modifier la valeur du sémaphore, le nombre de processus en attente sur le sémaphore, et d'autres choses bien pires encore, qu'il est possible d'utiliser, mais avec recul, précaution et modération...

+

À faire

+

Première approche : les fourchettes sont des ressources critiques

+

=> associer un sémaphore à chacune des fourchettes

+
    +
  • Implanter une version de base, où tous les philosophes commencent par prendre leur fourchette de droite avant de prendre leur fourchette de gauche.
  • +
  • Mettre en évidence la situation d'interblocage pouvant se produire avec cette version. Un moyen simple pour cela est d'introduire une temporisation suffisante entre les prises de fourchette.
  • +
  • Implanter (en utilisant des sémaphores) différentes adaptations de cette version de base afin d'éviter les interblocages. Justifier en quoi chaque adaptation évite les interblocages.
  • +
+

Seconde approche : contrôler la progression d'un philosophe en fonction de l'état de ses voisins.

+

=> introduire explicitement la notion d'état des philosophes, et associer un sémaphore « privé » à chaque philosophe :un philosophe peut manger si aucun de ses voisins ne mange, il doit attendre sinon. Les principales difficultés sont :

+
    +
  • d'assurer la cohérence des tests sur l'état des philosophes : le (dé)blocage d'un philosophe doit être effectué de manière simultanée (atomique) à l'évaluation de l'état qui motive la décision de (dé)blocage;
  • +
  • la gestion du déblocage d'un philosophe qui ne pouvait pas manger précédemment et qui peut le faire suite aux changements d'états d'un ou de ses deux voisins.
  • +
+

Cette solution est optimale en termes de degré de parallélisme, dans le sens où un philosophe qui demande et dont les voisins ne mangent pas peut manger directement. Evaluer ce degré de parallélisme, en fonction du nombre de philosophes, et le comparer avec celui de la solution 1.

+

Equité

+

Montrer (en exhibant un scénario avec 4 ou 5 philosophes) que cette solution « optimale » peut conduire à la famine d'un philosophe.

+

Imaginer une solution gérant une priorité entre les philosophes permettant de résoudre ce problème. Etudier

+
    +
  • le degré de parallélisme dans le pire des cas. Comparer avec la solution optimale.
  • +
  • l'attente maximum pour un philosophe demandeur (en termes nombre de philosophes servis avant le philosophe demandeur)
  • +
  • les limites éventuelles de la solution proposée.
  • +
+

Indications

+
    +
  • PhiloSem.java est la seule classe à modifier. Le constructeur de cette classe prend un paramètre correspondant au nombre de philosophes lancés. Les variables d'état ou les sémaphores utilisés par les méthodes de cette classes seront (déclarés comme) des attributs de cette classe.

  • +
  • Il est possible de contrôler la progression des philosophes pas à pas, en mettant la simulation en pause, puis en cliquant sur les philosophes (voir l'aide de la fenêtre), ce qui peut être très utile pour mettre en évidence des scénarios conduisant à des situations pathologiques (famine, erreur...)

  • +
  • Utiliser Main.java pour les numéros (Main.FourchetteGauche / Main.FourchetteDroite / Main.PhiloGauche / Main.PhiloDroite).

  • +
  • (Optionnel) Pour pour poser la fourchette n°f sur l'assiette à sa droite, à sa gauche ou sur la table, utiliser

    +
    IHMPhilo.poser (f, EtatFourchette.AssietteDroite);
    +IHMPhilo.poser (f, EtatFourchette.AssietteGauche);
    +IHMPhilo.poser (f, EtatFourchette.Table);
  • +
+ + diff --git a/TP3/README.md b/TP3/README.md new file mode 100644 index 0000000..d2e1a03 --- /dev/null +++ b/TP3/README.md @@ -0,0 +1,137 @@ +Problème des philosophes +======================== + +Énoncé +------ + +N philosophes sont autour d'une table. Il y a une assiette par philosophe, +et *une* fourchette entre chaque assiette. Pour manger, un philosophe +doit utiliser les deux fourchettes adjacentes à son assiette (et celles-là +seulement). + +Un philosophe peut être dans l'état : + +- penseur : il n'utilise pas de fourchettes ; +- mangeur : il utilise les deux fourchettes adjacentes ; aucun de ses + voisins ne peut manger ; +- demandeur : il souhaite manger mais ne dispose pas des deux fourchettes. + +Visuellement les états mangeur/demandeur/penseur sont représentés par un +rond noir (ou rouge en cas de possible problème)/un rond blanc/rien. + +Code fourni +----------- +- `StrategiePhilo.java` : interface de la synchronisation entre philosophes. +- `PhiloSem.java` : une implantation de cette interface. +- `ProcessusPhilosophe.java` : code d'un philosophe. +- `Main.java` : programme principal. + Définit aussi les `PhiloDroite(i)`, `PhiloGauche(i)`, `FourchetteGauche(i)`, + `FourchetteDroite(i)`. +- `EtatFourchette.java` : définition des constantes pour fourchette placée + sur la table, l'assiette gauche, l'assiette droite. +- `EtatPhilosophe.java` : définition des constantes pour philosophe penseur, + demandeur ou mangeur. +- `IHM*.java` : interface utilisateur. +- `Synchro/Simulateur.java` : le simulateur de temps. + +- Compilation: + `javac *.java Synchro/*.java` + +- Exécution: + `java Main` + `java Main PhiloSem 10` + (classe implantant l'interface StrategiePhilo) (nb de philosophes) + + Le bouton d'aide de la fenêtre affichée par l'application en présente + les fonctionnalités. + +La classe Semaphore +-------------------- +La plateforme Java fournit la classe `java.util.concurrent.Semaphore` qui propose +une implantation des sémaphores généraux, avec notamment : + +- un constructeur prenant un paramètre entier, correspondant à la valeur initiale +du sémaphore. Un second paramètre *optionnel* booléen, qui permet de préciser si +le sémaphore créé est FIFO. Par défaut, les sémaphores de +la classe `java.util.concurrent.Semaphore` ne sont pas FIFO. + Par exemple : `s=new Semaphore(5,true)` crée un sémaphore FIFO de valeur initiale 5. + +- une méthode `acquire()`, qui correspond à l'opération `P()` +- une méthode `release()`, qui correspond à l'opération `V()` + +Cette classe fournit en outre différentes méthodes destinées + à faciliter l'écriture des programmes (au risque d'altérer la clarté et la sûreté + de la conception). Il est ainsi possible d'effectuer des `P()`non bloquants (`tryAcquire()`), + de demander à augmenter ou diminuer la valeur du sémaphore de plusieurs unités en + une seule opération (`acquire(k)`, `release(j)`), de consulter et modifier + la valeur du sémaphore, le nombre de processus en attente sur le sémaphore, et + d'autres choses bien pires encore, qu'il est possible d'utiliser, + mais avec recul, précaution et modération... + +À faire +------- + +### Première approche : les fourchettes sont des ressources critiques +=> associer un sémaphore à chacune des fourchettes + +- Implanter une version de base, où tous les philosophes commencent par prendre leur +fourchette de droite avant de prendre leur fourchette de gauche. +- Mettre en évidence la situation d'interblocage pouvant se produire avec cette version. +Un moyen simple pour cela est d'introduire une temporisation suffisante entre les prises +de fourchette. +- Implanter (en utilisant des sémaphores) différentes adaptations de cette version de base +afin d'éviter les interblocages. Justifier en quoi chaque adaptation évite les interblocages. + +### Seconde approche : contrôler la progression d'un philosophe en fonction de l'état de ses voisins. +=> introduire explicitement la notion d'état des philosophes, et associer un sémaphore +« privé » à chaque philosophe :un philosophe peut manger si aucun de ses voisins ne mange, +il doit attendre sinon. Les principales difficultés sont : + +- d'assurer la cohérence des tests sur l'état des philosophes : le (dé)blocage d'un + philosophe doit être effectué de manière simultanée (atomique) à l'évaluation de + l'état qui motive la décision de (dé)blocage; +- la gestion du déblocage d'un philosophe qui ne pouvait pas manger précédemment et qui + peut le faire suite aux changements d'états d'un ou de ses deux voisins. + +Cette solution est optimale en termes de degré de parallélisme, dans +le sens où un philosophe qui demande et dont les voisins ne mangent pas peut +manger directement. Evaluer ce degré de parallélisme, en fonction du nombre +de philosophes, et le comparer avec celui de la solution 1. + +#### Equité +Montrer (en exhibant un scénario avec 4 ou 5 philosophes) que cette solution « optimale » +peut conduire à la famine d'un philosophe. + +Imaginer une solution gérant une priorité entre les philosophes permettant de résoudre ce +problème. Etudier + +- le degré de parallélisme dans le pire des cas. Comparer avec la solution optimale. +- l'attente maximum pour un philosophe demandeur + (en termes nombre de philosophes servis avant le philosophe demandeur) +- les limites éventuelles de la solution proposée. + +Indications +----------- + + - `PhiloSem.java` est la seule classe à modifier. Le constructeur de cette classe prend +un paramètre correspondant au nombre de philosophes lancés. Les variables d'état ou +les sémaphores utilisés par les méthodes de cette classes seront (déclarés comme) des +attributs de cette classe. + +- Il est possible de contrôler la progression des philosophes pas à pas, en mettant +la simulation en pause, puis en cliquant sur les philosophes (voir l'aide de la fenêtre), +ce qui peut être très utile pour mettre en évidence des scénarios conduisant à des +situations pathologiques (famine, erreur...) + + - Utiliser `Main.java` pour les numéros (`Main.FourchetteGauche` / +`Main.FourchetteDroite` / `Main.PhiloGauche` / `Main.PhiloDroite`). + + - (Optionnel) Pour pour poser la fourchette n°f sur l'assiette à *sa* droite, à *sa* + gauche ou sur la table, utiliser + + IHMPhilo.poser (f, EtatFourchette.AssietteDroite); + IHMPhilo.poser (f, EtatFourchette.AssietteGauche); + IHMPhilo.poser (f, EtatFourchette.Table); + + + diff --git a/TP3/StrategiePhilo.java b/TP3/StrategiePhilo.java new file mode 100644 index 0000000..7ec58c8 --- /dev/null +++ b/TP3/StrategiePhilo.java @@ -0,0 +1,17 @@ +// Time-stamp: <28 Oct 2008 17:09 queinnec@enseeiht.fr> + +/** Interface commune aux implantations de la gestion des fourchettes. */ +public interface StrategiePhilo { + /** Le philosophe no demande les fourchettes. + * Précondition : il n'en possède aucune. + * Postcondition : quand cette méthode retourne, il possède les deux fourchettes adjacentes à son assiette. */ + public void demanderFourchettes (int no) throws InterruptedException; + + /** Le philosophe no rend les fourchettes. + * Précondition : il possède les deux fourchettes adjacentes à son assiette. + * Postcondition : il n'en possède aucune. Les fourchettes peuvent être libres ou réattribuées à un autre philosophe. */ + public void libererFourchettes (int no) throws InterruptedException; + + /** Nom de cette stratégie (pour la fenêtre d'affichage). */ + public String nom(); +} diff --git a/TP3/Synchro/Assert.java b/TP3/Synchro/Assert.java new file mode 100644 index 0000000..8c57a38 --- /dev/null +++ b/TP3/Synchro/Assert.java @@ -0,0 +1,27 @@ +// Time-stamp: <02 mai 2013 10:16 queinnec@enseeiht.fr> + +package Synchro; + +/** À défaut d'un vrai assert... + * @see AssertionViolation + * + * @author Philippe Quéinnec + */ +public class Assert { + + /** Lève l'exception AssertionViolation si la + * condition b est fausse. + */ + public static void check (boolean b) { + if (! b) + throw new AssertionViolation (); + } + + /** Lève l'exception AssertionViolation avec le message + * msg si la condition b est fausse. + */ + public static void check (String msg, boolean b) { + if (! b) + throw new AssertionViolation (msg); + } +} diff --git a/TP3/Synchro/AssertionViolation.java b/TP3/Synchro/AssertionViolation.java new file mode 100644 index 0000000..5f84ea1 --- /dev/null +++ b/TP3/Synchro/AssertionViolation.java @@ -0,0 +1,18 @@ +// Time-stamp: <02 mai 2013 10:16 queinnec@enseeiht.fr> + +package Synchro; + +/** + * Exception levée en cas d'assertion invalide. + * Il s'agit d'une Error, qui ne doit pas être capturée + * ou traitée. + * @see Assert + * + * @author Philippe Quéinnec + */ +@SuppressWarnings("serial") +public class AssertionViolation extends Error +{ + public AssertionViolation () {} + public AssertionViolation (String s) {super(s);} +} diff --git a/TP3/Synchro/Simulateur.java b/TP3/Synchro/Simulateur.java new file mode 100644 index 0000000..65aea9b --- /dev/null +++ b/TP3/Synchro/Simulateur.java @@ -0,0 +1,258 @@ +// Time-stamp: <02 Apr 2008 16:09 queinnec@enseeiht.fr> + +package Synchro; + +import java.util.Random; +import java.util.Set; +import java.util.HashSet; +import java.util.Iterator; + +/** Simulateur temporel, avec possibilité de suspendre l'écoulement du temps, + * de varier la vitesse du temps, et d'interrompre un sommeil. + * + * @author Philippe Quéinnec + */ +public class Simulateur implements Runnable { + + // Utilisée pour bloquer un processus + private class Proc { + public int id; + public int duree_sommeil; + public boolean do_wakeup; + public Proc (int _id) { + id = _id; duree_sommeil = -1; do_wakeup = false; + } + } + + private Set procs; + + private int lastId = 0; + + private ThreadLocal procself; + + static private Random random = new Random(); + + // one system tick = 5ms + // one simulated tick = (1000/timespeed) system tick + // sleep(n) = Dormir n simulated ticks. + + private int timespeed= 1; + private int system_ticks = 1; + + // Le temps s'écoule-t-il ou pas ? + private boolean running = true; + + // private en fait + public void run () + { + while (true) { + try { + Thread.sleep (1); + } catch (InterruptedException e) {/*ignore*/} + synchronized (this) { + if (running) { + if (system_ticks == 0) { + //System.err.println ("."); + /* Décrémenter et réveiller les processus concernés, */ + Iterator it = procs.iterator(); + while (it.hasNext()) { + Proc p = it.next(); + synchronized (p) { + if (p.duree_sommeil == 0) { + //System.out.println ("wakeup1 "+p.id); + it.remove(); + p.do_wakeup = true; + p.notify(); + } + //System.out.println (i+"->"+procs[i].duree_sommeil); + p.duree_sommeil--; + } + } + system_ticks = 1000 / timespeed; + } else { + system_ticks--; + } + } else { // not running + try { + this.wait(); + } catch (InterruptedException e) { // nop + } + } + } // sync + } // while + } // run + + /** Initialise le simulateur de temps. */ + public Simulateur () + { + final Simulateur moi = this; + this.procs = new HashSet(); + this.procself = new ThreadLocal() { + protected Proc initialValue() { + synchronized (moi) { + return new Proc (moi.lastId++); + } + }}; + (new Thread (this)).start(); + } + + /** Deprecated. Pour Compatibilité. */ + public Simulateur (int nbprocs) + { + this(); + } + + /** Renvoie l'identité sous lequel est connu le thread appelant. */ + public int getId () + { + Proc p = procself.get(); + return p.id; + } + + /** Suspend l'exécution du processus appelant pour la durée spécifiée. */ + public void sleep (int duree) + { + Proc p = procself.get(); + sleepWithId (p.id, duree); + } + + /** Suspend l'exécution du processus appelant, qui s'identifie par + * noproc, pour la durée spécifiée. */ + public void sleepWithId (int noproc, int duree) + { + Proc p; + synchronized (this) { + if (noproc > lastId) { + lastId = noproc + 1; // juste au cas où... + } + p = procself.get(); + synchronized (p) { + procs.add (p); + p.id = noproc; + p.duree_sommeil = duree; + p.do_wakeup = false; + } + } + synchronized (p) { + while (! p.do_wakeup) { + try { + p.wait(); + } catch (InterruptedException e) {/*ignore*/} + } + } + } + + /** Suspend l'exécution du processus appelant pour une durée aléatoire + * comprise entre bi et bs. */ + public void sleep (int bi, int bs) + { + if (bi <= bs) + sleep (random.nextInt(bs - bi + 1) + bi); + } + + /** Suspend l'exécution du processus appelant, qui s'identifie par + * no, pour une durée aléatoire comprise entre + bi et bs. */ + public void sleepWithId (int no, int bi, int bs) + { + if (bi <= bs) + sleepWithId (no, random.nextInt(bs - bi + 1) + bi); + } + /** Compatibilité. */ + public void sleep (int no, int bi, int bs) + { + sleepWithId (no, bi, bs); + } + + /** Interrompt le sommeil du processus noproc. Sans effet si + * le processus ne dort pas. */ + public void wakeup (int noproc) + { + synchronized (this) { + Iterator it = procs.iterator(); + while (it.hasNext()) { + Proc p = it.next(); + synchronized (p) { + if (p.id == noproc) { + //System.out.println ("wakeup2 "+p.id); + it.remove(); + p.do_wakeup = true; + p.notify(); + } + } + } + } + } + + /** Renvoie la situation courante d'écoulement du temps. */ + public boolean getRunning () + { + synchronized (this) { + return running; + } + } + + /** Décide de l'écoulement du temps. */ + public void setRunning (boolean _running) + { + synchronized (this) { + running = _running; + if (running) + this.notify(); + } + } + + /** Inverse la situation courante d'écoulement du temps. */ + public void swapRunning () + { + synchronized (this) { + running = !running; + if (running) + this.notify(); + } + } + + /** Positionne la vitesse d'écoulement du temps. */ + public void setTimespeed (int _timespeed) + { + synchronized (this) { + timespeed = _timespeed; + system_ticks = 1000 / timespeed; + //System.out.println("Timespeed: "+timespeed); + } + } + + /** Obtention de la vitesse d'écoulement du temps. */ + public int getTimespeed () + { + synchronized (this) { + return timespeed; + } + } + + private boolean previous_running; + + /** Suspend l'écoulement du temps en sauvegardant la situation courante. + * Plusieurs appels successifs à suspendTime sans + alternance de resumeTime produisent un + comportement non spécifié. */ + public void suspendTime () + { + synchronized (this) { + previous_running = running; + running = false; + } + } + + /** Restaure la situation de l'écoulement du temps avant le précédent + * suspendTime. */ + public void resumeTime () + { + synchronized (this) { + running = previous_running; + if (running) + this.notify(); + } + } + +} diff --git a/TP4/IHMArgs.java b/TP4/IHMArgs.java new file mode 100644 index 0000000..5cf9523 --- /dev/null +++ b/TP4/IHMArgs.java @@ -0,0 +1,138 @@ +// Time-stamp: <03 mai 2013 11:12 queinnec@enseeiht.fr> + +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.event.*; +import java.util.Hashtable; +import java.io.File; +import java.io.FilenameFilter; +import java.util.List; +import java.util.LinkedList; + +public class IHMArgs extends JDialog { + + private int nbLect = 1; + private int nbRed = 1; + private int implantation = 0; + + public IHMArgs (Frame frame) + { + super(frame,"Arguments",true); + setLocationRelativeTo(frame); + + // Listener Fermeture du dialogue + addWindowListener(new WindowAdapter() { + public void windowClosing(WindowEvent e) + { + System.exit(0); + } + }); + + /* ===== choix de l'implantation ===== */ + final String[] choix = trouver_implantations("LectRed"); + JPanel jp_implantation = new JPanel(); + JComboBox jComboBox = new JComboBox(choix); + jComboBox.setSelectedIndex(implantation); + jComboBox.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + JComboBox source = (JComboBox) e.getSource(); + implantation = source.getSelectedIndex(); + } + }); + jp_implantation.add(jComboBox); + + /* ===== Nombre de Lecteurs ===== */ + final IHMChoixNombre jp_nbLec = new IHMChoixNombre(1,30,1,null); + + /* ===== Nombre de Redacteurs ===== */ + final IHMChoixNombre jp_nbRed = new IHMChoixNombre(1,30,1,null); + + /* ===== Boutons ===== */ + JPanel jp_boutons = new JPanel(new GridLayout(1,0,5,10)); + // OK + JButton jb_ok = new JButton("OK"); + jb_ok.addActionListener(new ActionListener() + { + public void actionPerformed(ActionEvent evt) + { + nbLect = jp_nbLec.getValeur(); + nbRed = jp_nbRed.getValeur(); + setVisible(false); + Main.initialiser (choix[implantation], nbLect, nbRed); + } + }); + jp_boutons.add(jb_ok); + // Annuler + JButton jb_annuler = new JButton("Annuler"); + jb_annuler.addActionListener(new ActionListener() + { + public void actionPerformed(ActionEvent evt) + { + System.exit (0); + } + }); + jp_boutons.add(jb_annuler); + /* ==== Assemblage ==== */ + Container contentPane = getContentPane(); + contentPane.add(new JLabel(" Implantation : ")); + contentPane.add(jp_implantation); + contentPane.add(new JLabel(" Lecteurs : ")); + contentPane.add(jp_nbLec); + contentPane.add(new JLabel(" Rédacteurs : ")); + contentPane.add(jp_nbRed); + contentPane.add(jp_boutons); + /* ==== Disposition ==== */ + GridBagLayout gridbag = new GridBagLayout(); + getContentPane().setLayout(gridbag); + // Contraintes + GridBagConstraints c = new GridBagConstraints(); + c.fill = GridBagConstraints.BOTH; + c.gridwidth = GridBagConstraints.REMAINDER; + gridbag.setConstraints(jp_implantation, c); + gridbag.setConstraints(jp_nbLec, c); + gridbag.setConstraints(jp_nbRed, c); + gridbag.setConstraints(jp_boutons, c); + + pack(); + setVisible(true); + } + + private String[] trouver_implantations (String interfaceName) + { + //String[] choix = {"Fifo", "PrioRedacteur", "SansStrategieHoare", "SansStrategieJava"}; + List lesChoix = new LinkedList(); + // Récupére les noms de fichier + String[] files = (new File(".")).list(); + // L'interface que les classes doivent implanter + Class interf = null; + try { + interf = Class.forName (interfaceName); + } catch (ClassNotFoundException e) { + System.err.println ("Panic: ne trouve pas l'interface "+interfaceName+" :"+e); + System.exit (1); + } + // Vérifions qu'ils implantent la bonne interface + for (int i = 0; i < files.length; i++) { + Class implant; + if (files[i].endsWith (".class")) { + String classname = files[i].substring (0, files[i].length()-6); + try { + implant = Class.forName (classname); + } catch (ClassNotFoundException e) { + implant = null; + } + if ((implant != null) && (! classname.equals(interfaceName)) && interf.isAssignableFrom (implant)) { + // ok ! + lesChoix.add (classname); + } + } + } + // Y a-t-il au moins une classe ? + if (lesChoix.isEmpty()) { + System.out.println ("Aucune implantation de "+interfaceName+" trouvee !"); + System.exit (1); + } + return lesChoix.toArray (new String[0]); + } +} diff --git a/TP4/IHMChoixNombre.java b/TP4/IHMChoixNombre.java new file mode 100644 index 0000000..8ca4379 --- /dev/null +++ b/TP4/IHMChoixNombre.java @@ -0,0 +1,93 @@ +// Time-stamp: <02 mai 2013 11:59 queinnec@enseeiht.fr> + +import javax.swing.*; +import javax.swing.event.*; +import java.awt.event.*; +import java.util.Hashtable; + +@SuppressWarnings("serial") +public class IHMChoixNombre extends JPanel implements ActionListener { + + private JTextField textField; + private JSlider js; + private int valeur; + private int min, max; + + public IHMChoixNombre(int min, int max, int ini, + ChangeListener otherChangeListener) + { + super(); + this.min = min; + this.max = max; + valeur = ini; + textField = new JTextField(Integer.toString(ini), 3); + textField.addActionListener(this); + textField.addFocusListener(new FocusAdapter() { + public void focusLost (FocusEvent e) { + actionPerformed (null); + } + }); + this.add(textField); + + js = new JSlider (JSlider.HORIZONTAL, min, max, ini); + js.addChangeListener(new ChangeListener() { + public void stateChanged(ChangeEvent e) { + JSlider source = (JSlider) e.getSource(); + valeur=source.getValue(); + textField.setText(Integer.toString(valeur)); + } + }); + if (otherChangeListener != null) + js.addChangeListener(otherChangeListener); + + js.setMajorTickSpacing(10); + js.setMinorTickSpacing(5); + js.setPaintTicks(true); + // crée table de labels + Hashtable labelTable = new Hashtable(); + labelTable.put( new Integer(min), new JLabel(Integer.toString(min)) ); + labelTable.put( new Integer(max), new JLabel(Integer.toString(max)) ); + js.setLabelTable (labelTable); + js.setPaintLabels (true); + + this.add(js); + } + + public void setEnabled(boolean bool) + { + textField.setEnabled(bool); + js.setEnabled(bool); + } + + public void actionPerformed(ActionEvent e) + { + try { + valeur = Integer.parseInt(textField.getText()); + if (valeur < min) { + textField.setText(Integer.toString(min)); + valeur = min; + } + else if (valeur > max) { + textField.setText(Integer.toString(max)); + valeur = max; + } + js.setValue(valeur); + } + catch (NumberFormatException exc) { + textField.setText(Integer.toString(min)); + valeur = min; + } + } + + public int getValeur () + { + return valeur; + } + + public void setInitialValue (int ini) + { + textField.setText(Integer.toString(ini)); + js.setValue(ini); + } + +} diff --git a/TP4/IHMLectRed.java b/TP4/IHMLectRed.java new file mode 100644 index 0000000..65badaf --- /dev/null +++ b/TP4/IHMLectRed.java @@ -0,0 +1,607 @@ +// Time-stamp: <30 mai 2013 10:32 queinnec@enseeiht.fr> +// v1 (10/14, PM) : Nettoyage applet, +// Correction message d'avertissement émis par le test de cohérence. + +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.event.*; +import java.util.*; +import java.util.List; +import java.util.Hashtable; +import Synchro.Simulateur; +import Synchro.ProcId; + +public class IHMLectRed +{ + // fenetre + public JFrame fenetre; + + // Zones de dessin + private JPanel jp_fenRed, jp_fenLec, jp_fenInside; + // rayon des cercles + private final int rayon = 7; + + private int nbLecteurs; + private int nbRedacteurs; + private List lesLecteurs = new LinkedList(); + private List lesRedacteurs = new LinkedList(); + private Map lesProcessus = new HashMap(); + + private Synchro.Simulateur simu; + private LectRed lr; + + private static Random random = new Random(); + + class Point { + int x; + int y; + } + + class ObjetGraphique { + ProcId id; // identité du processus correspondant + LectRedEtat etat; // Pour l'affichage + Point posRien; + Point posDemande; + Point posUtilise; + public ObjetGraphique(){ + posRien = new Point(); + posDemande = new Point(); + posUtilise = new Point(); + } + } + + /****************************************************************/ + + /** Cherche un dormeur dans ens qui soit dans l'état + * etat. + */ + private ObjetGraphique chercherDormeur (List ens, LectRedEtat etat) + { + int i, base; + int fin = ens.size(); + if (fin == 0) + return null; + // on tire au hasard le point de départ, et on parcourt circulairement. + base = random.nextInt (fin); + for (i = 0; i < fin; i++) { + int j = (base + i) % fin; + ObjetGraphique obj = ens.get(j); + if (obj.etat == etat) { + //simu.wakeup (obj.id); + return obj; + } + } + return null; + } + + /****************************************************************/ + + /** Actualise les variables nécessaires à l'affichage après un redimensionnement. */ + public void computePlacement () + { + int taille_fen_dem_x, taille_fen_inside_x; /* H1 dem = lect ou red */ + int taille_fen_dem_y, taille_fen_inside_y; + double intervRien, intervDemande, intervUtilise; + double startRien, startDemande, startUtilise; + + taille_fen_dem_x = jp_fenRed.getWidth(); + taille_fen_dem_y = jp_fenRed.getHeight(); + + taille_fen_inside_x = jp_fenInside.getWidth(); + taille_fen_inside_y = jp_fenInside.getHeight(); + + //System.out.println ("Placement = "+taille_fen_dem_x+"x"+taille_fen_dem_y); + + /* Les lecteurs */ + intervRien = taille_fen_dem_x / (nbLecteurs - 1 + 4.0); + intervUtilise = taille_fen_inside_x / (nbLecteurs - 1 + 4.0); + intervDemande = intervUtilise; + startRien = 2.0 * intervRien; + startUtilise = 2.0 * intervUtilise; + startDemande = (taille_fen_dem_x - taille_fen_inside_x) / 2 + startUtilise; + + for (ObjetGraphique obj : lesLecteurs) { + obj.posRien.x = (int) startRien; + obj.posRien.y = (int) (taille_fen_dem_y * 0.7); /* H3 */ + obj.posDemande.x = (int) startDemande; + obj.posDemande.y = (int) (taille_fen_dem_y * 0.3); /* H3 */ + obj.posUtilise.x = (int) startUtilise; + obj.posUtilise.y = (int) (taille_fen_inside_y * 0.7); /* H3 */ + startRien += intervRien; + startDemande += intervDemande; + startUtilise += intervUtilise; + } + + /* Les rédacteurs */ + intervRien = taille_fen_dem_x / (nbRedacteurs - 1 + 4.0); + intervUtilise = taille_fen_inside_x / (nbRedacteurs -1 + 4.0); + intervDemande = intervUtilise; + startRien = 2.0 * intervRien; + startUtilise = 2.0 * intervUtilise; + startDemande = (taille_fen_dem_x - taille_fen_inside_x) / 2 + startUtilise; + + for (ObjetGraphique obj : lesRedacteurs) { + obj.posRien.x = (int) startRien; + obj.posRien.y = (int) (taille_fen_dem_y * 0.3); /* H3 */ + obj.posDemande.x = (int) startDemande; + obj.posDemande.y = (int) (taille_fen_dem_y * 0.7); /* H3 */ + obj.posUtilise.x = (int) startUtilise; + obj.posUtilise.y = (int) (taille_fen_inside_y * 0.3); /* H3 */ + startRien += intervRien; + startDemande += intervDemande; + startUtilise += intervUtilise; + } + jp_fenRed.repaint(); + jp_fenInside.repaint(); + jp_fenLec.repaint(); + } + + /****************************************************************/ + + public void ajouterLecteur() + { + synchronized (this) { + nbLecteurs++; + ObjetGraphique obj = new ObjetGraphique(); + obj.etat = LectRedEtat.Lecteur_Rien; + obj.id = ProcId.getSelf(); + lesLecteurs.add (obj); + lesProcessus.put (obj.id, obj); + computePlacement(); + } + } + + public void ajouterRedacteur() + { + synchronized (this) { + nbRedacteurs++; + ObjetGraphique obj = new ObjetGraphique(); + obj.etat = LectRedEtat.Redacteur_Rien; + obj.id = ProcId.getSelf(); + lesRedacteurs.add (obj); + lesProcessus.put (obj.id, obj); + computePlacement(); + } + } + + public void enlever () + { + synchronized (this) { + ObjetGraphique obj = lesProcessus.get (ProcId.getSelf()); + lesProcessus.remove (obj.id); + boolean estLecteur; + if (lesLecteurs.contains (obj)) { + lesLecteurs.remove (obj); + nbLecteurs--; + } else { + lesRedacteurs.remove (obj); + nbRedacteurs--; + } + computePlacement(); + } + } + + /****************************************************************/ + + private void tracerCercle (Graphics g, boolean fill, Point unPt, int rayon) + { + g.setColor (Color.black); + if (fill) + g.fillOval(unPt.x - rayon, unPt.y - rayon, rayon*2, rayon*2); + else + g.drawOval(unPt.x - rayon, unPt.y - rayon, rayon*2, rayon*2); + } + + class FenLec extends JPanel { + public void paintComponent (Graphics g) + { + super.paintComponent(g); //paint background + for (ObjetGraphique obj : lesLecteurs) { + if (obj.etat == LectRedEtat.Lecteur_Rien) + tracerCercle (g, false, obj.posRien, rayon); + else if (obj.etat == LectRedEtat.Lecteur_Demande) + tracerCercle (g, false, obj.posDemande, rayon); + } + } + } + + class FenRed extends JPanel { + public void paintComponent (Graphics g) + { + super.paintComponent(g); //paint background + for (ObjetGraphique obj : lesRedacteurs) { + if (obj.etat == LectRedEtat.Redacteur_Rien) + tracerCercle (g, true, obj.posRien, rayon); + else if (obj.etat == LectRedEtat.Redacteur_Demande) + tracerCercle (g, true, obj.posDemande, rayon); + } + } + } + + class FenInside extends JPanel { + public void paintComponent (Graphics g) + { + super.paintComponent(g); //paint background + for (ObjetGraphique obj : lesRedacteurs) { + if (obj.etat == LectRedEtat.Redacteur_Ecrit) + tracerCercle (g, true, obj.posUtilise, rayon); + } + for (ObjetGraphique obj : lesLecteurs) { + if (obj.etat == LectRedEtat.Lecteur_Lit) + tracerCercle (g, false, obj.posUtilise, rayon); + } + } + } + + /****************************************************************/ + + private Color defColor; + + private void checkValidState () { + int nr, nl; + + if (defColor == null) + defColor = jp_fenInside.getBackground(); + + nl = 0; nr = 0; + for (ObjetGraphique obj : lesLecteurs) { + if (obj.etat == LectRedEtat.Lecteur_Lit) + nl++; + } + for (ObjetGraphique obj : lesRedacteurs) { + if (obj.etat == LectRedEtat.Redacteur_Ecrit) + nr++; + } + if (((nl > 0) && (nr > 0)) || (nr > 1)) { + System.err.println("Attention : peut-être nblect="+nl+" nbred="+nr); + jp_fenInside.setBackground(Color.RED); + } else { + jp_fenInside.setBackground(defColor); + } + } + + public void changerEtat (final LectRedEtat etat) + { + final ObjetGraphique obj = lesProcessus.get (ProcId.getSelf()); + SwingUtilities.invokeLater(new Runnable (){ + public void run() { + // assert ((no >= 0) && (no < nbObjets)); + // LECTEUR => (etat = Demande, Lit ou Rien) + // assert ((no >= nbLecteurs) || (etat == LecRedEtat.Lecteur_Demande) + // || (etat == Lecteur_Lit) || (etat == Lecteur_Rien)); + // REDACTEUR => (etat = Demande, Ecrit ou Rien) + // assert ((no < nbLecteurs) || (etat == Redacteur_Demande) + // || (etat == Redacteur_Ecrit) || (etat == Redacteur_Rien)); + obj.etat = etat; + checkValidState(); + if (etat == LectRedEtat.Lecteur_Rien) { + jp_fenInside.repaint(); + jp_fenLec.repaint(); + } + else if (etat == LectRedEtat.Lecteur_Lit) { + jp_fenInside.repaint(); + jp_fenLec.repaint(); + } + else if (etat == LectRedEtat.Lecteur_Demande) { + jp_fenLec.repaint(); + } + else if (etat == LectRedEtat.Redacteur_Rien) { + jp_fenInside.repaint(); + jp_fenRed.repaint(); + } + else if (etat == LectRedEtat.Redacteur_Ecrit) { + jp_fenInside.repaint(); + jp_fenRed.repaint(); + } + else if (etat == LectRedEtat.Redacteur_Demande) { + jp_fenRed.repaint(); + } + //System.out.println("Changement etat: "+no+": "+etat); + } + }); + } + + /****************************************************************/ + public IHMLectRed (LectRed _lr, + Synchro.Simulateur _simu) + { + nbLecteurs = 0; + nbRedacteurs = 0; + simu = _simu; + lr = _lr; + fenetre = new JFrame("Lecteurs/Rédacteurs"); + + // Listener Fermeture de la fenetre + fenetre.setDefaultCloseOperation (JFrame.EXIT_ON_CLOSE); + + // Listener touche 'q' + fenetre.addKeyListener (new KeyAdapter() + { + public void keyTyped (KeyEvent e) + { + if (e.getKeyChar() == 'q') { + fenetre.dispatchEvent (new WindowEvent(fenetre,WindowEvent.WINDOW_CLOSING)); + //System.exit (0); + } + } + }); + /* ===== Boutons ===== */ + JPanel jp_boutons = new JPanel(new GridLayout(0,4,5,10)); + +// if (! isApplet) { + // Quitter + JButton jb_quitter = new JButton(" Quitter "); + jb_quitter.addActionListener(new ActionListener() + { + public void actionPerformed(ActionEvent evt) + { + //System.out.println("Fermeture de Lecteurs/Rédacteurs..."); + //Main.quitter(); // pour tester + System.exit(0); + } + }); + jp_boutons.add(jb_quitter); + // } + + // Parametres + final JDialog dialogParam = new IHMParametres (null); + JButton jb_parametres = new JButton("Paramètres"); + jb_parametres.setToolTipText("Paramétrage fin du comportement"); + jb_parametres.addActionListener(new ActionListener() + { + public void actionPerformed(ActionEvent evt) + { + dialogParam.setVisible (true); + } + }); + jp_boutons.add(jb_parametres); + + // Pause + final JButton jb_pause = new JButton(" Pause "); + jb_pause.setToolTipText("Suspension/reprise du temps"); + jb_pause.addActionListener(new ActionListener() + { + public void actionPerformed(ActionEvent evt) + { + simu.swapRunning(); + if (simu.getRunning()) + jb_pause.setText("Pause"); + else + jb_pause.setText("Temps suspendu"); + } + }); + jp_boutons.add(jb_pause); + + // Aide + JEditorPane jep_aide = + new JEditorPane("text/html", + "
"+ + "Lecteurs/Rédacteurs
-------------------
"+ + "\nProblème : deux classes de processus sont en compétition pour accéder à un fichier; "+ + " les lecteurs peuvent être concurrents qu'entre eux, et les rédacteurs sont exclusifs"+ + " vis-à-vis de tous.

"+ + "Interprétation du dessin :"+ + "
  • les disques noirs sont les rédacteurs, et les cercles noirs sont les lecteurs;"+ + "
  • un processus dans le rectangle central possède l'accès;"+ + "
  • un processus à proximité du rectangle demande l'accès."+ + "
"+ + "Actions :"+ + "
  • en cliquant dans le rectangle, vous forcez une fin d'utilisation;
  • "+ + "
  • en cliquant côté lecteur, vous forcez une demande de lecture;
  • "+ + "
  • en cliquant côté rédacteur, vous forcez une demande d'écriture;
  • "+ + "
  • pause permet de suspendre le temps de la simulation."+ + " Les actions forcées sont par contre toujours possibles;
  • "+ + "
  • vous pouvez régler la vitesse de la simulation avec l'échelle du bas.
  • "+ + "
"+ + ""); + jep_aide.setEditable (false); + JOptionPane pane = new JOptionPane(new JScrollPane (jep_aide)); + final JDialog dialogAide = pane.createDialog(null,"Aide"); + dialogAide.setModal (false); + dialogAide.setSize(500,550); + JButton jb_aide = new JButton(" Aide "); + jb_aide.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent evt) + { + dialogAide.setVisible (true); + }}); + jp_boutons.add(jb_aide); + + /* ===== Le nom de la stratégie ===== */ + JPanel jp_strategie = new JPanel(); + jp_strategie.add(new JLabel(lr.nomStrategie())); + jp_strategie.setBorder(BorderFactory.createEtchedBorder()); + + + /* ===== Les fenêtres de dessin ===== */ + /* la fenêtre rédacteur est en haut, la fenêtre lecteur en bas. */ + + // ==== Rédacteurs ==== + JPanel jp_titreRed = new JPanel(); + jp_titreRed.add(new JLabel("REDACTEURS")); + + jp_fenRed = new FenRed(); + jp_fenRed.setToolTipText("Cliquer pour forcer une demande de rédaction"); + //jp_fenRed.setBorder(BorderFactory.createEtchedBorder()); + jp_fenRed.addMouseListener(new MouseAdapter() + { + public void mouseClicked(MouseEvent e) + { + ObjetGraphique obj = chercherDormeur (lesRedacteurs, LectRedEtat.Redacteur_Rien); + if (obj != null) + simu.wakeup (obj.id); + } + }); + + JButton jb_red_plus = new JButton("+1"); + jb_red_plus.setToolTipText("Ajouter un rédacteur"); + final IHMLectRed moi = this; + jb_red_plus.addActionListener(new ActionListener() + { + public void actionPerformed(ActionEvent evt) + { + new ProcessusRedacteur (lr,simu,moi).start(); + } + }); + + JButton jb_red_moins = new JButton("-1"); + jb_red_moins.setToolTipText("Enlever un rédacteur endormi"); + jb_red_moins.setMaximumSize(jb_red_plus.getMaximumSize()); + jb_red_moins.addActionListener(new ActionListener() + { + public void actionPerformed(ActionEvent evt) + { + ObjetGraphique obj = chercherDormeur (lesRedacteurs, LectRedEtat.Redacteur_Rien); + if (obj != null) + simu.wakeupAndDie (obj.id); + } + }); + + Box box_bouton_red = Box.createVerticalBox(); + box_bouton_red.add (Box.createVerticalGlue()); + box_bouton_red.add (jb_red_plus); + box_bouton_red.add (jb_red_moins); + box_bouton_red.add (Box.createVerticalGlue()); + Box box_red = Box.createHorizontalBox(); + box_red.add (box_bouton_red); + box_red.add (jp_fenRed); + + // ==== Inside ==== + jp_fenInside = new FenInside(); + jp_fenInside.setToolTipText("Cliquer pour forcer une sortie"); + jp_fenInside.setBorder(BorderFactory.createTitledBorder("Fichier")); + jp_fenInside.addMouseListener(new MouseAdapter() + { + public void mouseClicked(MouseEvent e) + { + ObjetGraphique obj; + obj = chercherDormeur (lesLecteurs, LectRedEtat.Lecteur_Lit); + if (obj == null) + obj = chercherDormeur (lesRedacteurs, LectRedEtat.Redacteur_Ecrit); + if (obj != null) + simu.wakeup (obj.id); + } + }); + + // ==== Lecteurs ==== + jp_fenLec = new FenLec(); + jp_fenLec.setToolTipText("Cliquer pour forcer une demande de lecture"); + // jp_fenLec.setBorder(BorderFactory.createEtchedBorder()); + JPanel jp_titreLec = new JPanel(); + jp_titreLec.add(new JLabel("LECTEURS")); + jp_fenLec.addMouseListener(new MouseAdapter() + { + public void mouseClicked(MouseEvent e) + { + ObjetGraphique obj = chercherDormeur (lesLecteurs, LectRedEtat.Lecteur_Rien); + if (obj != null) + simu.wakeup (obj.id); + } + }); + + JButton jb_lec_plus = new JButton("+1"); + jb_lec_plus.setToolTipText("Ajouter un lecteur"); + jb_lec_plus.addActionListener(new ActionListener() + { + public void actionPerformed(ActionEvent evt) + { + new ProcessusLecteur (lr,simu,moi).start(); + } + }); + + JButton jb_lec_moins = new JButton("-1"); + jb_lec_moins.setMaximumSize(jb_lec_plus.getMaximumSize()); + jb_lec_moins.setToolTipText("Enlever un lecteur endormi"); + jb_lec_moins.addActionListener(new ActionListener() + { + public void actionPerformed(ActionEvent evt) + { + ObjetGraphique obj = chercherDormeur (lesLecteurs, LectRedEtat.Lecteur_Rien); + if (obj != null) + simu.wakeupAndDie (obj.id); + } + }); + + Box box_bouton_lec = Box.createVerticalBox(); + box_bouton_lec.add (Box.createVerticalGlue()); + box_bouton_lec.add (jb_lec_plus); + box_bouton_lec.add (jb_lec_moins); + box_bouton_lec.add (Box.createVerticalGlue()); + Box box_lec = Box.createHorizontalBox(); + box_lec.add (box_bouton_lec); + box_lec.add (jp_fenLec); + + /* ===== Le réglage de vitesse du temps ===== */ + JPanel jp_vitesse = new JPanel(); + jp_vitesse.setToolTipText("Vitesse d'écoulement du temps simulé"); + jp_vitesse.setBorder(BorderFactory.createEtchedBorder()); + jp_vitesse.add(new JLabel(" Vitesse du temps : ")); + JSlider js_vitesseTemps = new JSlider(JSlider.HORIZONTAL,1,100,1); + /* Event "value_changed" de l'ajustement de l'échelle de vitesse du temps. */ + js_vitesseTemps.addChangeListener(new ChangeListener() { + public void stateChanged(ChangeEvent e) { + JSlider source = (JSlider) e.getSource(); + simu.setTimespeed((int)source.getValue()); + } + }); + js_vitesseTemps.setMajorTickSpacing(10); + js_vitesseTemps.setPaintTicks(true); + // crée table de labels + Hashtable labelTable = new Hashtable(); + labelTable.put( new Integer( 1 ), new JLabel("1") ); + labelTable.put( new Integer( 100 ), new JLabel("100") ); + js_vitesseTemps.setLabelTable( labelTable ); + js_vitesseTemps.setPaintLabels(true); + jp_vitesse.add(js_vitesseTemps); + + /* ===== Assemblage ===== */ + Container contentPane = fenetre.getContentPane(); + contentPane.add(jp_boutons); + contentPane.add(jp_strategie); + contentPane.add(jp_titreRed); + contentPane.add(box_red); + contentPane.add(jp_fenInside); + contentPane.add(box_lec); + contentPane.add(jp_titreLec); + contentPane.add(jp_vitesse); + + /* ==== Contraintes ==== */ + GridBagLayout gridbag = new GridBagLayout(); + contentPane.setLayout(gridbag); + // Contraintes pour fenêtres de dessin + GridBagConstraints c_fen = new GridBagConstraints(); + c_fen.gridwidth = GridBagConstraints.REMAINDER; + c_fen.fill = GridBagConstraints.BOTH; + c_fen.weightx = 1.0; + c_fen.weighty = 1.0; + gridbag.setConstraints(box_red, c_fen); + gridbag.setConstraints(jp_fenInside,c_fen); + gridbag.setConstraints(box_lec, c_fen); + // Contraintes pour les autres éléments + GridBagConstraints c = new GridBagConstraints(); + c.gridwidth = GridBagConstraints.REMAINDER; + c.fill = GridBagConstraints.BOTH; + gridbag.setConstraints(jp_boutons, c); + gridbag.setConstraints(jp_strategie, c); + gridbag.setConstraints(jp_titreRed, c); + gridbag.setConstraints(jp_titreLec, c); + gridbag.setConstraints(jp_vitesse, c); + + // Listener redimensionnement de la fenetre + contentPane.addComponentListener(new ComponentAdapter() + { + public void componentResized(ComponentEvent e) + { + //System.out.println ("Resized"); + computePlacement (); + } + }); + /* ===== Affichage ==== */ + int taille_fen_x = Math.max(2*7*(Math.max(nbRedacteurs,nbLecteurs)+10),400); + int taille_fen_y = 400; + //fenetre.pack(); + fenetre.setSize(taille_fen_x,taille_fen_y); + fenetre.setVisible(true); + } +} diff --git a/TP4/IHMParametres.java b/TP4/IHMParametres.java new file mode 100644 index 0000000..049149d --- /dev/null +++ b/TP4/IHMParametres.java @@ -0,0 +1,119 @@ +// Time-stamp: <03 mai 2013 11:12 queinnec@enseeiht.fr> + +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.event.*; + +public class IHMParametres extends JDialog { + + // les fréquences actuellement en action + private int freqLect = 2; + private int freqRed = 2; + + // les nouvelles fréquences en cours de choix, avant confirmation + // (ok ou appliquer) + private int newfreqLect; + private int newfreqRed; + + private final static String[] choix = { + "Peu fréquents", "Assez fréquents", "Fréquents", + "Très fréquents", "Extrêmement fréquents" + }; + + public IHMParametres (Frame frame) + { + super(frame, "Paramètres de la simulation", false); + //setLocationRelativeTo(frame); + + // ===== Fréquence de Lecteurs ===== + JPanel jp_Lec = new JPanel(); + jp_Lec.add (new JLabel (" Fréquence des lecteurs : ")); + final JComboBox jc_Lec = new JComboBox (choix); + //jc_Lec.setSelectedIndex (freqLect); + jc_Lec.addActionListener (new ActionListener() { + public void actionPerformed(ActionEvent e) { + JComboBox source = (JComboBox) e.getSource(); + newfreqLect = source.getSelectedIndex(); + } + }); + jp_Lec.add(jc_Lec); + + // ===== Fréquence des Redacteurs ===== + JPanel jp_Red = new JPanel(); + jp_Red.add (new JLabel(" Fréquence des redacteurs : ")); + final JComboBox jc_Red = new JComboBox(choix); + //jc_Red.setSelectedIndex (freqRed); + jc_Red.addActionListener (new ActionListener() { + public void actionPerformed(ActionEvent e) { + JComboBox source = (JComboBox) e.getSource(); + newfreqRed = source.getSelectedIndex(); + } + }); + jp_Red.add (jc_Red); + + this.addComponentListener (new ComponentAdapter() { + public void componentShown (ComponentEvent e) { + jc_Lec.setSelectedIndex (freqLect); + jc_Red.setSelectedIndex (freqRed); + newfreqLect = freqLect; + newfreqRed = freqRed; + }}); + + // ===== Boutons ===== + JPanel jp_boutons = new JPanel(new GridLayout(0,3,5,10)); + // OK + JButton jb_ok = new JButton("OK"); + jb_ok.addActionListener (new ActionListener() + { + public void actionPerformed(ActionEvent evt) + { + freqLect = newfreqLect; + freqRed = newfreqRed; + Main.setSleepDuration (freqLect, freqRed); + setVisible (false); + } + }); + jp_boutons.add(jb_ok); + // Appliquer + JButton jb_appli = new JButton("Appliquer"); + jb_appli.addActionListener (new ActionListener() + { + public void actionPerformed (ActionEvent evt) + { + freqLect = newfreqLect; + freqRed = newfreqRed; + Main.setSleepDuration (freqLect, freqRed); + } + }); + jp_boutons.add(jb_appli); + // Annuler + JButton jb_annuler = new JButton ("Annuler"); + jb_annuler.addActionListener (new ActionListener() + { + public void actionPerformed (ActionEvent evt) + { + setVisible(false); + } + }); + jp_boutons.add (jb_annuler); + // ==== Assemblage ==== + Container contentPane = getContentPane(); + contentPane.add(jp_Lec); + contentPane.add(jp_Red); + contentPane.add(jp_boutons); + // ==== Contraintes ==== + GridBagLayout gridbag = new GridBagLayout(); + getContentPane().setLayout(gridbag); + // Contraintes + GridBagConstraints c = new GridBagConstraints(); + c.fill = GridBagConstraints.BOTH; + c.gridwidth = GridBagConstraints.REMAINDER; + gridbag.setConstraints(jp_Lec, c); + gridbag.setConstraints(jp_Red, c); + gridbag.setConstraints(jp_boutons, c); + + pack(); + } + +} diff --git a/TP4/LectRed.java b/TP4/LectRed.java new file mode 100644 index 0000000..d0902d8 --- /dev/null +++ b/TP4/LectRed.java @@ -0,0 +1,10 @@ +// Time-stamp: <02 Apr 2008 16:26 queinnec@enseeiht.fr> + +public interface LectRed { + + public void demanderLecture () throws InterruptedException; + public void terminerLecture () throws InterruptedException; + public void demanderEcriture () throws InterruptedException; + public void terminerEcriture () throws InterruptedException; + public String nomStrategie (); +} diff --git a/TP4/LectRedEtat.java b/TP4/LectRedEtat.java new file mode 100644 index 0000000..f5a3000 --- /dev/null +++ b/TP4/LectRedEtat.java @@ -0,0 +1,10 @@ +// Time-stamp: <24 fév 2010 09:41 queinnec@enseeiht.fr> + +public enum LectRedEtat { + Redacteur_Demande, + Redacteur_Ecrit, + Redacteur_Rien, + Lecteur_Demande, + Lecteur_Lit, + Lecteur_Rien +} diff --git a/TP4/LectRed_FIFO.java b/TP4/LectRed_FIFO.java new file mode 100644 index 0000000..e6f11fb --- /dev/null +++ b/TP4/LectRed_FIFO.java @@ -0,0 +1,82 @@ +// Time-stamp: <08 Apr 2008 11:35 queinnec@enseeiht.fr> + +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +/** + * Lecteurs/rédacteurs stratégie d'ordonnancement: priorité FIFO, implantation: + * avec un moniteur. + */ +public class LectRed_FIFO implements LectRed { + + Lock lock; + Condition requestAccess; + Condition SAS; + boolean activeWriter; + int activeReaders; + boolean SASoccupied; + int FIFOsize; + + public LectRed_FIFO() { + lock = new ReentrantLock(); + requestAccess = lock.newCondition(); + SAS = lock.newCondition(); + activeWriter = false; + activeReaders = 0; + SASoccupied = false; + FIFOsize = 0; + } + + public void demanderLecture() throws InterruptedException { + lock.lock(); + if (activeWriter || FIFOsize > 0) { + FIFOsize++; + requestAccess.await(); + FIFOsize--; + } + activeReaders++; + requestAccess.signal(); + lock.unlock(); + } + + public void terminerLecture() throws InterruptedException { + lock.lock(); + activeReaders--; + if (activeReaders == 0) { + if (SASoccupied) { + SAS.signal(); + } else { + requestAccess.signal(); + } + } + lock.unlock(); + } + + public void demanderEcriture() throws InterruptedException { + lock.lock(); + if (activeWriter || activeReaders > 0 || FIFOsize > 0 || SASoccupied) { + FIFOsize++; + requestAccess.await(); + FIFOsize--; + } + if (activeReaders > 0) { + SASoccupied = true; + SAS.await(); + SASoccupied = false; + } + activeWriter = true; + lock.unlock(); + } + + public void terminerEcriture() throws InterruptedException { + lock.lock(); + activeWriter = false; + requestAccess.signal(); + lock.unlock(); + } + + public String nomStrategie() { + return "Stratégie: Moniteur, prio FIFO."; + } +} diff --git a/TP4/LectRed_PrioLecteur.java b/TP4/LectRed_PrioLecteur.java new file mode 100644 index 0000000..9c511ab --- /dev/null +++ b/TP4/LectRed_PrioLecteur.java @@ -0,0 +1,65 @@ +// Time-stamp: <08 Apr 2008 11:35 queinnec@enseeiht.fr> + +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +/** + * Lecteurs/rédacteurs stratégie d'ordonnancement: priorité aux lecteurs, + * implantation: avec un moniteur. + */ +public class LectRed_PrioLecteur implements LectRed { + + Lock lock; + Condition requestRead; + Condition requestWrite; + boolean activeWriter; + int activeReaders; + + public LectRed_PrioLecteur() { + lock = new ReentrantLock(); + requestRead = lock.newCondition(); + requestWrite = lock.newCondition(); + activeWriter = false; + activeReaders = 0; + } + + public void demanderLecture() throws InterruptedException { + lock.lock(); + if (activeWriter) { + requestRead.await(); + } + activeReaders++; + lock.unlock(); + } + + public void terminerLecture() throws InterruptedException { + lock.lock(); + activeReaders--; + if (activeReaders == 0) { + requestWrite.signal(); + } + lock.unlock(); + } + + public void demanderEcriture() throws InterruptedException { + lock.lock(); + while (activeWriter || activeReaders > 0) { + requestWrite.await(); + } + activeWriter = true; + lock.unlock(); + } + + public void terminerEcriture() throws InterruptedException { + lock.lock(); + activeWriter = false; + requestRead.signalAll(); + requestWrite.signalAll(); + lock.unlock(); + } + + public String nomStrategie() { + return "Stratégie: Moniteur, prio lecteur."; + } +} diff --git a/TP4/LectRed_PrioRedacteur.java b/TP4/LectRed_PrioRedacteur.java new file mode 100644 index 0000000..2e2cb41 --- /dev/null +++ b/TP4/LectRed_PrioRedacteur.java @@ -0,0 +1,69 @@ +// Time-stamp: <08 Apr 2008 11:35 queinnec@enseeiht.fr> + +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +/** + * Lecteurs/rédacteurs stratégie d'ordonnancement: priorité aux rédacteurs, + * implantation: avec un moniteur. + */ +public class LectRed_PrioRedacteur implements LectRed { + + Lock lock; + Condition requestRead; + Condition requestWrite; + boolean activeWriter; + int activeReaders; + int waitingWriters; + + public LectRed_PrioRedacteur() { + lock = new ReentrantLock(); + requestRead = lock.newCondition(); + requestWrite = lock.newCondition(); + activeWriter = false; + activeReaders = 0; + waitingWriters = 0; + } + + public void demanderLecture() throws InterruptedException { + lock.lock(); + while (activeWriter || waitingWriters > 0) { + requestRead.await(); + } + activeReaders++; + lock.unlock(); + } + + public void terminerLecture() throws InterruptedException { + lock.lock(); + activeReaders--; + if (activeReaders == 0) { + requestWrite.signal(); + } + lock.unlock(); + } + + public void demanderEcriture() throws InterruptedException { + lock.lock(); + waitingWriters++; + while (activeWriter || activeReaders > 0) { + requestWrite.await(); + } + waitingWriters--; + activeWriter = true; + lock.unlock(); + } + + public void terminerEcriture() throws InterruptedException { + lock.lock(); + activeWriter = false; + requestWrite.signalAll(); + requestRead.signalAll(); + lock.unlock(); + } + + public String nomStrategie() { + return "Stratégie: Moniteur, prio rédacteur."; + } +} diff --git a/TP4/Main.java b/TP4/Main.java new file mode 100644 index 0000000..03a902f --- /dev/null +++ b/TP4/Main.java @@ -0,0 +1,208 @@ +// Time-stamp: <02 mai 2013 10:22 queinnec@enseeiht.fr> +// v1 (10/14, PM) : Nettoyage applet + +import Synchro.Simulateur; + +public class Main +{ + static final boolean verbose = false; + + static private LectRed lr; + static private int nblecteurs; + static private int nbredacteurs; + + /* Chaque lecteur lit entre MinDelayLit et MaxDelayLit secondes, et + * pense entre MinDelayLRien et MaxDelayLRien (les deux bornes incluses). */ + /* Chaque rédacteur écrit entre MinDelayEcrit et MaxDelayEcrit secondes, et + * pense entre MinDelayERien et MaxDelayERien (les deux bornes incluses). */ + static int MinDelayLRien; + static int MaxDelayLRien; + static int MinDelayERien; + static int MaxDelayERien; + static final int MinDelayLit = 5; + static final int MaxDelayLit = 20; + static final int MinDelayEcrit = 2; + static final int MaxDelayEcrit = 10; + + /* L'intervalle de sommeil est tjs calculé par rapport à l'intervalle + * d'utilisation. */ + static void setSleepDuration (int freql, int freqr) + { + switch (freql) { + case 0: /* Peu gourmands */ + MinDelayLRien = 20 * MinDelayLit; + MaxDelayLRien = 20 * MaxDelayLit; + break; + case 1: /* Assez gourmands */ + MinDelayLRien = 5 * MinDelayLit; + MaxDelayLRien = 5 * MaxDelayLit; + break; + case 2: /* gourmands (initial) */ + MinDelayLRien = MinDelayLit; + MaxDelayLRien = MaxDelayLit; + break; + case 3: /* Très gourmands */ + MinDelayLRien = (MinDelayLit + 1) / 3; + MaxDelayLRien = (MaxDelayLit + 1) / 3; + break; + case 4: /* Extrêmement gourmands */ + MinDelayLRien = (MinDelayLit + 1) / 8; + MaxDelayLRien = (MaxDelayLit + 1) / 8; + break; + default: + if (verbose) + System.out.println ("Frequence de sommeil hors des bornes.\n"); + } + if (verbose) + System.out.println ("Lecteur: Delai pense ="+ MinDelayLRien +"-"+MaxDelayLRien+", utilise = "+ MinDelayLit+"-"+MaxDelayLit); + switch (freqr) { + case 0: /* Peu gourmands */ + MinDelayERien = 20 * MinDelayEcrit; + MaxDelayERien = 20 * MaxDelayEcrit; + break; + case 1: /* Assez gourmands */ + MinDelayERien = 5 * MinDelayEcrit; + MaxDelayERien = 5 * MaxDelayEcrit; + break; + case 2: /* gourmands (initial) */ + MinDelayERien = MinDelayEcrit; + MaxDelayERien = MaxDelayEcrit; + break; + case 3: /* Très gourmands */ + MinDelayERien = (MinDelayEcrit + 1) / 3; + MaxDelayERien = (MaxDelayEcrit + 1) / 3; + break; + case 4: /* Extrêmement gourmands */ + MinDelayERien = (MinDelayEcrit + 1) / 8; + MaxDelayERien = (MaxDelayEcrit + 1) / 8; + break; + default: + if (verbose) + System.out.println ("Frequence de sommeil hors des bornes."); + } + if (verbose) + System.out.println ("Redacteur: Delai pense ="+ MinDelayERien +"-"+MaxDelayERien+", utilise = "+ MinDelayEcrit+"-"+MaxDelayEcrit); + + } + + static public void initialiser (String nomImplantation, int _nblecteurs, int _nbredacteurs) { + + lr = charger_implantation ("LectRed", nomImplantation); + if (lr == null) { + System.err.println ("Abandon."); + System.exit (1); + } + + nblecteurs = _nblecteurs; + nbredacteurs = _nbredacteurs; + + ThreadGroup thg = new ThreadGroup ("L/R"); + + if (verbose) + System.out.println(lr.nomStrategie()+" - Lecteurs: "+nblecteurs+" - Redacteurs: "+nbredacteurs); + + Simulateur simu = new Simulateur (thg); + + setSleepDuration (2, 2); + IHMLectRed ihm = new IHMLectRed (lr, simu); + + simu.start(); + + for (int i = 0; i < nblecteurs; i++) { + new ProcessusLecteur(lr,simu,ihm).start(); + } + for (int i = 0; i < nbredacteurs; i++) { + new ProcessusRedacteur(lr,simu,ihm).start(); + } + ihm.computePlacement(); + + } + + /** Crée un objet à partir de l'implantation implName, qui doit implanter + * l'interface interfName. */ + private static LectRed charger_implantation (String interfName, String implName) + { + LectRed res = null; + + // Obtenir l'interface interfName + Class interf; + try { + interf = Class.forName (interfName); + } catch (ClassNotFoundException e) { + System.err.println ("Panic: ne trouve pas l'interface "+interfName+" :"+e); + return null; + } + + // Trouve la classe implName (ou interfName_implName) + Class implant = null; + try { + implant = Class.forName (implName); + } catch (ClassNotFoundException e1) { + try { + implant = Class.forName (interfName+"_"+implName); + } catch (ClassNotFoundException e2) { + System.err.println ("Impossible de trouver la classe "+implName+": "+e1); + return null; + } + } + + // Vérifie qu'elle implante la bonne interface + if (! interf.isAssignableFrom (implant)) { + System.err.println ("La classe "+implant.getName()+" n'implante pas l'interface "+interf.getName()+"."); + return null; + } + + // Crée une instance + try { + Class[] consparam = { }; + java.lang.reflect.Constructor cons = implant.getConstructor (consparam); + Object[] initargs = { }; + res = (LectRed) cons.newInstance (initargs); + } catch (NoSuchMethodException e) { + System.err.println ("Classe "+implant.getName()+": pas de constructeur adequat: "+e); + } catch (InstantiationException e) { + System.err.println ("Echec instation "+implant.getName()+": "+e); + } catch (IllegalAccessException e) { + System.err.println ("Echec instation "+implant.getName()+": "+e); + } catch (java.lang.reflect.InvocationTargetException e) { + System.err.println ("Echec instation "+implant.getName()+": "+e); + if (e.getCause() != null) { + System.err.println (" La cause est : " + e.getCause() + + " in " + (e.getCause().getStackTrace())[0]); + } + } catch (ClassCastException e5) { + System.err.println ("Echec instation "+implant.getName()+": n'est pas un "+interfName+": "+e5); + } + return res; + } + + public static void main (String args []) + { + int nbArgs = args.length; + + if ((nbArgs != 0) && (nbArgs != 3)) { + System.out.println("java Main "); + System.exit (1); + } + if (nbArgs == 0) { + new IHMArgs (null); + /* NO RETURN */ + } else { + String implantation = args[0]; + + int nbLecteurs = Integer.parseInt (args[1]); + if (nbLecteurs < 1) { + System.out.println("Nb lecteur >= 1"); + System.exit (1); + } + int nbRedacteurs = Integer.parseInt (args[2]); + if (nbRedacteurs < 1) { + System.out.println("Nb redacteur >= 1"); + System.exit (1); + } + + initialiser (implantation, nbLecteurs, nbRedacteurs); + } + } +} + diff --git a/TP4/ProcessusLecteur.java b/TP4/ProcessusLecteur.java new file mode 100644 index 0000000..82b240e --- /dev/null +++ b/TP4/ProcessusLecteur.java @@ -0,0 +1,57 @@ +// Time-stamp: <02 Apr 2008 16:27 queinnec@enseeiht.fr> +// v1 (10/14, PM) : Ajout d'une petite temporisation sur les transitions critiques (accès) +// pour éviter un décalage entre l'état de l'application et l'état des objets grqphiques +// au moment du test de cohérence réalisé par l'IHM graphique. Pas très glorieux, mais +// a priori inévitable, à partir du moment où l'affichage et la simulation sont découplés + +import Synchro.Simulateur; +import Synchro.ProcId; + +public class ProcessusLecteur extends Thread +{ + private int no; + private LectRed lr; + private Simulateur simu; + private IHMLectRed ihm; + private int SYNCHRO_AFFICHAGE = 10; + + public ProcessusLecteur (LectRed lr, Simulateur simu, IHMLectRed ihm) { + super (simu.getThreadGroup(), ""); + this.no = no; + this.lr = lr; + this.simu = simu; + this.ihm = ihm; + } + + public void run() { + setName ("Lecteur-"+ProcId.getSelf()); + ihm.ajouterLecteur(); + try { + simu.sleep (0, Main.MaxDelayLRien/2); + while (true) { + // demande à lire + ihm.changerEtat (LectRedEtat.Lecteur_Demande); + lr.demanderLecture (); + Thread.sleep(SYNCHRO_AFFICHAGE); + + ihm.changerEtat (LectRedEtat.Lecteur_Lit); + + // utilise + simu.sleep (Main.MinDelayLit, Main.MaxDelayLit); + + lr.terminerLecture (); + ihm.changerEtat (LectRedEtat.Lecteur_Rien); + + // pense + simu.sleep (Main.MinDelayLRien, Main.MaxDelayLRien); + } + } catch (Synchro.Suicide e) { + // nothing + } catch (InterruptedException e2) { + // nothing + } finally { + ihm.enlever(); + } + } +} + diff --git a/TP4/ProcessusRedacteur.java b/TP4/ProcessusRedacteur.java new file mode 100644 index 0000000..a1b4851 --- /dev/null +++ b/TP4/ProcessusRedacteur.java @@ -0,0 +1,56 @@ +// Time-stamp: <02 Apr 2008 16:27 queinnec@enseeiht.fr> +// v1 (10/14, PM) : Ajout d'une petite temporisation sur les transitions critiques (accès) +// pour éviter un décalage entre l'état de l'application et l'état des objets grqphiques +// au moment du test de cohérence réalisé par l'IHM graphique. Pas très glorieux, mais +// a priori inévitable, à partir du moment où l'affichage et la simulation sont découplés + +import Synchro.Simulateur; +import Synchro.ProcId; + +public class ProcessusRedacteur extends Thread +{ + private int no; + private LectRed lr; + private Simulateur simu; + private IHMLectRed ihm; + private int SYNCHRO_AFFICHAGE = 10; + + public ProcessusRedacteur(LectRed lr, Simulateur simu, IHMLectRed ihm) { + super (simu.getThreadGroup(), ""); + this.no = no; + this.lr = lr; + this.simu = simu; + this.ihm = ihm; + } + + public void run() { + setName ("Redacteur-"+ProcId.getSelf()); + ihm.ajouterRedacteur(); + try { + simu.sleep (0, Main.MaxDelayERien/2); + while (true) { + // demande à écrire + ihm.changerEtat (LectRedEtat.Redacteur_Demande); + lr.demanderEcriture (); + Thread.sleep(SYNCHRO_AFFICHAGE); + ihm.changerEtat (LectRedEtat.Redacteur_Ecrit); + + // utilise + simu.sleep (Main.MinDelayEcrit, Main.MaxDelayEcrit); + + lr.terminerEcriture(); + ihm.changerEtat (LectRedEtat.Redacteur_Rien); + + // pense + simu.sleep (Main.MinDelayERien, Main.MaxDelayERien); + } + } catch (Synchro.Suicide e) { + // nothing + } catch (InterruptedException e2) { + // nothing + } finally { + ihm.enlever(); + } + } +} + diff --git a/TP4/README.html b/TP4/README.html new file mode 100644 index 0000000..7225527 --- /dev/null +++ b/TP4/README.html @@ -0,0 +1,32 @@ + + + + + + + + + + +

Problème des lecteurs/rédacteurs

+

Objectif

+

Écrire des implantations de LectRed.java.

+

Stratégies à implanter

+
    +
  • priorité aux rédacteurs ou aux lecteurs

  • +
  • équitable (absence de famine que ce soient des lecteurs ou des rédacteurs)

  • +
+

Compilation

+

javac *.java Synchro/*.java

+

Exécution

+

java Main

+
    +
  • Le programme trouve automatiquement toutes les implantations disponibles dans le répertoire (par réflexivité), et en particulier les nouvelles implantations rajoutées.
  • +
  • Le bouton d'aide de la fenêtre affichée par l'application en présente les fonctionnalités.
  • +
+

Il est par ailleurs également possible de lancer une implantation particulière par :

+

java Main <l'implantation écrite> <nb lecteurs> <nb rédacteurs>

+

par exemple:

+

java Main MonImplantation 6 4

+ + diff --git a/TP4/README.md b/TP4/README.md new file mode 100644 index 0000000..e816d96 --- /dev/null +++ b/TP4/README.md @@ -0,0 +1,77 @@ +Problème des lecteurs/rédacteurs +================================ + +Objectif +-------- + +Écrire des implantations de LectRed.java. + +Stratégies à implanter + +- priorité aux rédacteurs ou aux lecteurs + +- équitable (absence de famine que ce soient des lecteurs ou des + rédacteurs) + +Compilation +----------- + +`javac *.java Synchro/*.java` + +Exécution +--------- + +`java Main` + +- Le programme trouve automatiquement toutes les implantations + disponibles dans le répertoire (par réflexivité), et en particulier + les nouvelles implantations rajoutées. +- Le bouton d’aide de la fenêtre affichée par l’application en + présente les fonctionnalités. + +Il est par ailleurs également possible de lancer une implantation +particulière par : + +`java Main ` + +par exemple: + +`java Main MonImplantation 6 4`des lecteurs/rédacteurs +================================ + +Objectif +-------- + +Écrire des implantations de LectRed.java. + +Stratégies à implanter + +- priorité aux rédacteurs ou aux lecteurs + +- équitable (absence de famine que ce soient des lecteurs ou des + rédacteurs) + +Compilation +----------- + +`javac *.java Synchro/*.java` + +Exécution +--------- + +`java Main` + +- Le programme trouve automatiquement toutes les implantations + disponibles dans le répertoire (par réflexivité), et en particulier + les nouvelles implantations rajoutées. +- Le bouton d’aide de la fenêtre affichée par l’application en + présente les fonctionnalités. + +Il est par ailleurs également possible de lancer une implantation +particulière par : + +`java Main ` + +par exemple: + +`java Main MonImplantation 6 4` diff --git a/TP4/Synchro/Assert.java b/TP4/Synchro/Assert.java new file mode 100644 index 0000000..8c57a38 --- /dev/null +++ b/TP4/Synchro/Assert.java @@ -0,0 +1,27 @@ +// Time-stamp: <02 mai 2013 10:16 queinnec@enseeiht.fr> + +package Synchro; + +/** À défaut d'un vrai assert... + * @see AssertionViolation + * + * @author Philippe Quéinnec + */ +public class Assert { + + /** Lève l'exception AssertionViolation si la + * condition b est fausse. + */ + public static void check (boolean b) { + if (! b) + throw new AssertionViolation (); + } + + /** Lève l'exception AssertionViolation avec le message + * msg si la condition b est fausse. + */ + public static void check (String msg, boolean b) { + if (! b) + throw new AssertionViolation (msg); + } +} diff --git a/TP4/Synchro/AssertionViolation.java b/TP4/Synchro/AssertionViolation.java new file mode 100644 index 0000000..5f84ea1 --- /dev/null +++ b/TP4/Synchro/AssertionViolation.java @@ -0,0 +1,18 @@ +// Time-stamp: <02 mai 2013 10:16 queinnec@enseeiht.fr> + +package Synchro; + +/** + * Exception levée en cas d'assertion invalide. + * Il s'agit d'une Error, qui ne doit pas être capturée + * ou traitée. + * @see Assert + * + * @author Philippe Quéinnec + */ +@SuppressWarnings("serial") +public class AssertionViolation extends Error +{ + public AssertionViolation () {} + public AssertionViolation (String s) {super(s);} +} diff --git a/TP4/Synchro/ProcId.java b/TP4/Synchro/ProcId.java new file mode 100644 index 0000000..83c21ed --- /dev/null +++ b/TP4/Synchro/ProcId.java @@ -0,0 +1,61 @@ +// Time-stamp: <02 mai 2013 10:16 queinnec@enseeiht.fr> + +package Synchro; + +/** Identification de processus. + * Opérations: ProcId.getId(), equals(ProcId) et equals(int). + * + * En interne, contient un entier unique, non recyclé, utile pour les traces + * (il ne sert à rien d'autre sinon). + */ +public class ProcId { + + private static ThreadLocal clefId; + + private static Object verrouLastId = new Object(); + private static int lastId = 0; + + private int id; + + private ProcId (int id) { + this.id = id; + } + + /** Obtention de l'identité du processus appelant. + * S'il n'en avait pas, on lui en crée une. + */ + public static ProcId getSelf() + { + if (clefId == null) { + synchronized (verrouLastId) { + if (clefId == null) { + clefId = new ThreadLocal() { + protected ProcId initialValue() { + synchronized (verrouLastId) { + return new ProcId (lastId++); + } + }}; + } + } + } + return clefId.get(); + } + + public boolean equals (Object o) + { + if (! (o instanceof ProcId)) + return false; + return (((ProcId)o).id == this.id); + } + + public boolean equals (int id) + { + return (id == this.id); + } + + public String toString() + { + return ""+id; + } + +} diff --git a/TP4/Synchro/Simulateur.java b/TP4/Synchro/Simulateur.java new file mode 100644 index 0000000..b0c8e0c --- /dev/null +++ b/TP4/Synchro/Simulateur.java @@ -0,0 +1,238 @@ +// Time-stamp: <02 mai 2013 10:16 queinnec@enseeiht.fr> + +package Synchro; + +import java.util.Random; +import java.util.Set; +import java.util.HashSet; +import java.util.Iterator; + +/** Simulateur temporel, avec possibilité de suspendre l'écoulement du temps, + * de varier la vitesse du temps, et d'interrompre un sommeil. + * + * @author Philippe Quéinnec + */ +public class Simulateur implements Runnable { + + // Utilisée pour bloquer un processus + private class Proc { + public ProcId id; + public int duree_sommeil = -1; + public boolean do_wakeup = false; + public boolean must_die = false; + public Proc (ProcId _id) { + id = _id; + } + }; + + /** Le groupe de thread où se trouve le simulateur. */ + private ThreadGroup thg; + + /** L'ensemble des processus actuellement bloqués. */ + private Set procs; + + static private Random random = new Random(); + + // one system tick = 5ms + // one simulated tick = (1000/timespeed) system tick + // sleep(n) = Dormir n simulated ticks. + + private int timespeed= 1; + private int system_ticks = 1; + + // Le temps s'écoule-t-il ou pas ? + private boolean running = true; + + // private en fait + public void run () + { + while (true) { + try { + Thread.sleep (1); + } catch (InterruptedException e) { } + synchronized (this) { + if (running) { + if (system_ticks == 0) { + //System.err.println ("."); + /* Décrémenter et réveiller les processus concernés, */ + Iterator it = procs.iterator(); + while (it.hasNext()) { + Proc p = it.next(); + synchronized (p) { + if (p.duree_sommeil == 0) { + //System.out.println ("wakeup1 "+p.id); + it.remove(); + p.do_wakeup = true; + p.notify(); + } + //System.out.println (i+"->"+procs[i].duree_sommeil); + p.duree_sommeil--; + } + } + system_ticks = 1000 / timespeed; + } else { + system_ticks--; + } + } else { // not running + try { + this.wait(); + } catch (InterruptedException e) { // nop + } + } + } // sync + } // while + } // run + + /** Initialise le simulateur de temps. */ + public Simulateur (ThreadGroup thg) + { + this.procs = new HashSet(); + this.thg = thg; + } + + /** Active le simulateur. */ + public void start () + { + (new Thread (this.thg, this, "Simulateur")).start(); + } + + /** Renvoie le groupe de thread dans lequel se trouve le simulateur. */ + public ThreadGroup getThreadGroup() + { + return thg; + } + + /** Suspend l'exécution du processus appelant, qui s'identifie par + * noproc, pour la durée spécifiée. */ + public void sleep (int duree) + { + Proc p = new Proc (ProcId.getSelf()); + synchronized (this) { + procs.add (p); + p.duree_sommeil = duree; + p.do_wakeup = false; + } + synchronized (p) { + while (! p.do_wakeup) { + try { + p.wait(); + } catch (InterruptedException e) {} + } + } + if (p.must_die) + throw new Suicide(); + } + + /** Suspend l'exécution du processus appelant pour une durée aléatoire + * comprise entre bi et bs. */ + public void sleep (int bi, int bs) + { + if (bi <= bs) + sleep (random.nextInt(bs - bi + 1) + bi); + } + + + /** Interrompt le sommeil du processus noproc. Sans effet si + * le processus ne dort pas. */ + private void wakeupAndDie (ProcId id, boolean mustDie) + { + synchronized (this) { + Iterator it = procs.iterator(); + while (it.hasNext()) { + Proc p = it.next(); + synchronized (p) { + if (p.id.equals (id)) { + //System.out.println ("wakeup2 "+p.id); + it.remove(); + p.do_wakeup = true; + p.must_die = mustDie; + p.notify(); + } + } + } + } + } + + /** Interrompt le sommeil du processus noproc. Sans effet si + * le processus ne dort pas. */ + public void wakeup (ProcId id) + { + wakeupAndDie (id, false); + } + public void wakeupAndDie (ProcId id) + { + wakeupAndDie (id, true); + } + + /** Renvoie la situation courante d'écoulement du temps. */ + public boolean getRunning () + { + synchronized (this) { + return running; + } + } + + /** Décide de l'écoulement du temps. */ + public void setRunning (boolean _running) + { + synchronized (this) { + running = _running; + if (running) + this.notify(); + } + } + + /** Inverse la situation courante d'écoulement du temps. */ + public void swapRunning () + { + synchronized (this) { + running = !running; + if (running) + this.notify(); + } + } + + /** Positionne la vitesse d'écoulement du temps. */ + public void setTimespeed (int _timespeed) + { + synchronized (this) { + timespeed = _timespeed; + system_ticks = 1000 / timespeed; + //System.out.println("Timespeed: "+timespeed); + } + } + + /** Obtention de la vitesse d'écoulement du temps. */ + public int getTimespeed () + { + synchronized (this) { + return timespeed; + } + } + + private boolean previous_running; + + /** Suspend l'écoulement du temps en sauvegardant la situation courante. + * Plusieurs appels successifs à suspendTime sans + alternance de resumeTime produisent un + comportement non spécifié. */ + public void suspendTime () + { + synchronized (this) { + previous_running = running; + running = false; + } + } + + /** Restaure la situation de l'écoulement du temps avant le précédent + * suspendTime. */ + public void resumeTime () + { + synchronized (this) { + running = previous_running; + if (running) + this.notify(); + } + } + +} diff --git a/TP4/Synchro/Suicide.java b/TP4/Synchro/Suicide.java new file mode 100644 index 0000000..7391846 --- /dev/null +++ b/TP4/Synchro/Suicide.java @@ -0,0 +1,8 @@ +// Time-stamp: <02 mai 2013 10:16 queinnec@enseeiht.fr> + +package Synchro; + +@SuppressWarnings("serial") +public class Suicide extends RuntimeException +{ +} diff --git a/TP4/rappel_moniteur.md b/TP4/rappel_moniteur.md new file mode 100644 index 0000000..dd06f17 --- /dev/null +++ b/TP4/rappel_moniteur.md @@ -0,0 +1,46 @@ +Moniteurs en Java (rappel) : +-------------------- + + - Créer un objet de type java.util.concurrent.locks.Lock + (à partir de java.util.concurrent.locks.ReentrantLock) : + `    mon_moniteur = new java.util.concurrent.locks.ReentrantLock();` + Cet objet réalise le moniteur en fournissant, par son utilisation + explicite, l'exclusion mutuelle et permet de créer des + variables-conditions associées au verrou. + `    ` + *Remarque* : Il est possible de créer un ReentrantLock équitable (mais pas nécessairement FIFO) : + `    mon_moniteur = new java.util.concurrent.locks.ReentrantLock(true);` + + + - Créer une ou des variables-conditions (java.util.concurrent.locks.Condition) + à partir du "lock" précédemment créé : + + `ma_var_condition = mon_moniteur.newCondition();` + + - Les opérations doivent explicitement manipuler le verrou pour obtenir + l'exclusion mutuelle : + + `mon_opération() {` + ` mon_moniteur.lock();` + + ` ... ` + + ` mon_moniteur.unlock(); ` + ` }` + + - Les variables-conditions s'utilisent avec + + `    ma_var_condition.await();` + et + `    ma_var_condition.signal();` + `    ma_var_condition.signalAll();` + + - La sémantique est priorité au signaleur, sans file des signalés : + * en outre, l'attente sur les conditions n'est **pas FIFO**, et + * le signaleur conserve l'accès exclusif + * le signalé est réveillé, mais doit réacquérir l'accès exclusif, et il n'est pas + prioritaire par rapport aux entrants en attente (que le verrou soit équitable ou non) ; + il doit donc nécessairement retester la condition attendue, car elle a pu être utilisée/ + invalidée par un entrant avant qu'il obtienne l'accès exclusif. + Il faut donc penser à utiliser des boucles "while" pour déterminer si les conditions + attendues sont effectivement vérifiées au moment du réveil. \ No newline at end of file diff --git a/TP4/reponses.md b/TP4/reponses.md new file mode 100644 index 0000000..967db27 --- /dev/null +++ b/TP4/reponses.md @@ -0,0 +1,19 @@ +# TP4 Systèmes Concurrents + +## Stratégie 1 : Prio Lecteur + +Dans cette stratégie, les lecteurs ont la priorité sur les rédacteurs. +Cela signifie que pour accéder au fichier un écrivain doit attendre qu'il n'y ait plus aucun lecteur actif sur le fichier. +Cela peut entrainer une famine chez les écrivains s'il reste toujours au moins un lecteur sur le fichier. + +## Stratégie 2 : Prio Rédacteur + +Dans cette stratégie, les rédacteurs ont la priorité sur les lecteurs. +Cela signifie que pour accéder au fichier un lecteur doit attendre qu'il n'y ait pas d'écriture active ou bien qu'un écrivain ne soit pas en attente d'accès au fichier. +Cela peut entrainer une famine chez les lecteurs s'il reste toujours au moins un écrivain dans la liste d'attente des écrivains. + +## Stratégie 3 : Prio FIFO + +Dans cette stratégie, les rédacteurs et lecteurs sont placés dans une FIFO lors de leur demande d'accès. +Cela implique une équité d'accès pour les lecteurs et rédacteurs, il n'y a donc pas de famine +(si l'on considère les temps d'accès au fichier finis) diff --git a/TP5/Contenu.md b/TP5/Contenu.md new file mode 100644 index 0000000..2974595 --- /dev/null +++ b/TP5/Contenu.md @@ -0,0 +1,8 @@ +- comptage des mots d'un répertoire : find+grep (versions fork/join et récursive) +- données : tableaux générés pat GCVT (non intégrés à l'archive) +- exemples énoncé : contient le source des exemples de l'énoncé, avec des variantes complémentaires + (FJG : exemple ForkJoin, pool fixe : exemple somme) +- README.html : sujet (format html) +- README.md : : sujet (format markdown) +- max : calcul du maximum d'un tableau (mono, pool, forkjoin) + utilitaire de gestion de tableaux (GCVT) +- tri fusion : (mono, pool, forkjoin) diff --git a/TP5/README.html b/TP5/README.html new file mode 100644 index 0000000..526b099 --- /dev/null +++ b/TP5/README.html @@ -0,0 +1,411 @@ + + + + + + + + + + + + +

Parallélisme régulé

+

Objectifs

+
    +
  • gérer le parallélisme à gros grain
  • +
  • paralléliser un algorithme par décomposition en sous-tâches
  • +
  • connaître les services d'exécution de la plateforme Java
  • +
+

Prérequis

+

Vous devez savoir parfaitement comment définir une activité (Thread) en Java, comment lancer une activité, et + comment attendre sa terminaison.

+

** Si ce n'est pas le cas, (re)voyez et (re)faites le travail demandé à la rubrique « concurrence et cohérence » + avant d'entamer ce TP**

+

Vous aurez vraisemblablement besoin lors de ce TP d'utiliser les méthodes de classe suivantes de la classe + Thread :

+
    +
  • static Thread currentThread() qui fournit la référence du thread en cours d'exécution
  • +
  • static void sleep(long ms) throws InterruptedException qui suspend le thread appelant pour une + durée de ms millisecondes
  • +
+

Enfin, vous aurez sans doute aussi besoin de deux méthodes de classe de la classe System : + System.nanoTime() et System.currentTimeMillis() qui fournissent une durée écoulée (en + ns et ms) depuis une date d'origine non spécifiée. La différence entre les valeurs retournées par deux appels + successifs permet d'évaluer le temps écoulé entre ces deux appels.

+

Préparation : services de régulation des activités + en Java

+

La rapide présentation qui suit peut être complétée par la lecture de la partie correspondante du cours sur + les processus légers (planches 34-45) pour les notions et sur la documentation + Java en ligne pour la syntaxe et les détails techniques.

+

Les classes et notions utilisées jusqu'ici étaient destinées à définir et gérer la concurrence explicitement, et + à un niveau fin : le choix de lancer, d'attendre et de terminer une tâche appartient entièrement au programmeur. + De même, le programmeur a la charge des choix en termes de gestion de la cohérence (variables + volatile, classes atomiques...) et du type d'attente (blocs synchronized, verrous, + attente active).

+

La plateforme Java fournit dans ses dernières versions la classe Executor, destinée à séparer la + gestion des activités des aspects purement applicatifs. Le principe est qu'un objet de la classe + Executor (« exécuteur ») fournit un service de gestion et d'ordonnancement d'activités, + auquel on soumet des tâches à traiter. Une application est donc vue comme un ensemble de tâches qui + sont fournies à l'exécuteur. L'exécuteur gère alors l'exécution des tâches qui lui sont soumises de manière + indépendante et transparente pour l'application. L'objectif de ce service est de permettre

+
    +
  • de simplifier la tâche du programmeur, puisqu'il n'a plus à gérer le démarrage des activités, ni leur + ordonnancement
  • +
  • d'adapter le nombre d'activités exécutées à la charge et au nombre de processeurs physiques disponibles
  • +
+

Le paquetage java.util.concurrent définit 3 interfaces pour les exécuteurs :

+
    +
  • Executor, qui fournit une méthode execute, permettant de soumettre une tâche + Runnable.
  • +
  • ExecutorService, qui étend Executor, avec une méthode submit, + permettant de soumettre une tâche Callable et renvoyant un objet Future, lequel + permet de récupérer la valeur de retour de la tâche Callable soumise. Un + ExecutorService permet en outre de soumettre des ensembles de tâches Callable, et + de gérer la terminaison de l'exécuteur.
  • +
  • ScheduledExecutorService, qui étend ExecutorService avec des méthodes permettant + de spécifier l'ordonnancement des tâches soumises.
  • +
+

Le paquetage java.util.concurrent fournit différentes implémentations d'exécuteurs. Le principe + commun aux exécuteurs est de distribuer les tâches soumises à un ensemble d'ouvriers. Chaque ouvrier est un + thread cyclique, qui traite une par une les tâches qui lui sont attribuées.

+

Les exécuteurs fournis par le paquetage java.util.concurrent sont de deux sortes :

+

Pools de threads

+

La classe java.util.concurrent.Executors fournit des méthodes permettant de créer des pools de + threads implantant ExecutorService avec un nombre d'ouvriers fixe -- méthode + newFixedThreadPool --, variable (adaptable) -- méthode newCachedThreadPool) ou + permettant une régulation par vol de tâches (voir cours) (méthode newWorkStealingPool). Une + variante implantant ScheduledExecutorService est proposée pour chacune de ces méthodes, afin de + permettre d'intervenir sur l'ordonnancement des tâches. Enfin, les classes + java.util.concurrent.ThreadPoolExecutor et + java.util.concurrent.ScheduledThreadPoolExecutor proposent encore davantage d'options sur la + paramétrage et la supervision de l'ordonnancement.

+

Les pools de threads évitent la création de nouveaux threads pour chaque tâche à traiter, puisque qu'un même + ouvrier est réutilisé pour traiter une suite de tâches, ce qui présente plusieurs avantages :

+
    +
  • éviter la création de threads apporte un gain (significatif lorsque les tâches sont nombreuses) en termes de + consommation de ressources mémoire et processeur,
  • +
  • le délai de prise en charge des requêtes est réduit du temps de la création du traitant de la requête,
  • +
  • enfin, et surtout, le contrôle du nombre d'ouvriers va permettre de réguler et d'adapter l'exécution en + fonction des ressources matérielles disponibles, au lieu d'avoir une exécution directement dépendante du + flux de tâches à traiter. Ainsi, dans le cas d'un flux de tâches augmentant brutalement (comme dans le cas + d'une attaque par déni de service), les performances se dégraderont progressivement (car le délai de prise + en charge augmentera proportionnellement au nombre de tâches en attente), mais il n'y aura pas d'écroulement + dû à un épuisement des ressources nécessaires.
  • +
+

D'une manière générale,

+
    +
  • Le choix ou l'adaptation du nombre d'ouvriers en fonction du nombre de processeurs effectivement disponibles + et de la charge courante est un élément clé de la parallélisation avec un pool de threads : trop peu + nombreux, les ouvriers ne pourront exploiter tous les processeurs ; trop nombreux, il mobiliseront des + ressources inutilement et auront un impact négatif sur les performances. +
      +
    • Note : l'appel de la méthode Runtime.getRuntime().availableProcessors() fournit le + nombre de processeurs disponibles pour la JVM courante.
    • +
    +
  • +
  • Les pools de threads sont bien adaptés au traitement de problèmes réguliers, c'est à dire aux problèmes + décomposables en sous-problèmes de « taille » équivalente, ce qui garantit une bonne répartition des tâches + entre ouvriers.
  • +
+
Classes et méthodes utiles
+
    +
  • la classe java.util.concurrent.Executors, permet de créer des pools de threads par appel de + newFixedThreadPool() ou newCachedThreadPool() (cf supra)
  • +
  • la classe ExecutorService et sa superclasse Executor, définissent l'interface d'un + exécuteur, avec notamment les méthodes submit(), execute() (cf supra) et + shutdown() (gestion de la terminaison de l'exécuteur)
  • +
  • la classe Future fournit (immédiatement) une référence vers le résultat (à venir) d'une tâche + Callablesoumise à l'exécuteur par submit(). L'appel de la méthode + get() permet d'obtenir le résultat effectif, en attendant s'il n'est pas encore disponible. +
  • +
  • les tâches ne renvoyant pas de résultat sont des Runnable, soumises à l'exécuteur par + execute().
  • +
+
Un exemple
+
import java.util.concurrent.Future;
+import java.util.concurrent.Callable;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ExecutorService;
+
+class SigmaC implements Callable<Long> {
+    private long début;
+    private long fin;
+
+    SigmaC(long d, long f) { début = d; fin = f;}
+
+    @Override
+    public  Long call() { // le résultat doit être un objet
+        long s = 0;
+        for (long i = début; i <= fin; i++) s = s + i;
+        return s;
+    }                
+}
+
+class SigmaR implements Runnable {
+    private long début;
+    private long fin;
+
+    SigmaR(long d, long f) { début = d; fin = f;}
+
+    @Override
+    public  void run() {
+        long s = 0;
+        for (long i = début; i <= fin; i++) s = s + i;
+        System.out.println("Calcul terminé. ∑("+début+","+fin+") = "+s);
+    }                
+}
+
+public class Somme {     
+    public static void main(String[] args) throws Exception {
+    
+        ExecutorService poule = Executors.newFixedThreadPool(2);
+           
+        Future<Long> f1 = poule.submit(new SigmaC(0L,1_000_000_000L));
+        Future<Long> f2 = poule.submit(new SigmaC(0L,4_000_000_000L));
+        poule.execute(new SigmaR(900_000L,1_000_000_000L));
+        Future<Long> f3 = poule.submit(new SigmaC(1,100));
+        Future<Long> f4 = poule.submit(new SigmaC(0L,3_000_000_000L));
+    
+        poule.shutdown();
+    
+        System.out.println("Résultat obtenu. f1 = "+f1.get());
+        System.out.println("Résultat obtenu. f2 = "+f2.get());        
+        System.out.println("Résultat obtenu. f3 = "+f3.get());        
+        System.out.println("Résultat obtenu. f4 = "+f4.get());
+    }    
+}
+
Commentaires
+
    +
  • L'application crée un pool avec un nombre fixe d'ouvriers (2), puis lance 5 tâches : les deux premières et + les deux dernières soumises (Callable , soumises par appel à submit()) rendent un + résultat Future, récupéré de manière bloquante par l'appel à la méthode get(). La + troisième (Runnable, soumise par appel à execute()) s'exécute de manière + asynchrone.
  • +
  • L'exécution voit la tâche Runnable terminer après la première soumise (f1), car + bien que plus courte, elle ne peut démarrer tant que l'une des deux premières tâches lancées n'est pas + terminée, la taille du pool étant de 2. L'appel f2.get() entraîne l'attente de la terminaison + de f2, plus longue que f1et la tâche Runnable cumulées. L'appel de + f3.get() retourne immédiatement, car f3, courte est déjà terminé. L'appel + f4.get() entraîne l'attente de la terminaison de f4.
  • +
  • shutdown permet de terminer proprement l'exécuteur, qui dès lors n'accepte plus de nouvelles + tâches. L'application Java termine avec la dernière tâche traitée. Si shutdown est omis, + l'application ne peut terminer, car les threads de l'exécuteur restent en attente de nouvelles tâches.
  • +
  • L'archive contient une variante (SommePlus) de l'application Somme, qui illustre + l'utilisation de : +
      +
    • invokeAll() sur une collection de tâches/actions pour soumettre une collection (ici une + liste) de Callable. Les résultats sont alors rendus dans une liste de + Future;
    • +
    • get() sur les Future de cette liste, pour récupérer les résultats + effectifs
    • +
    +
  • +
+

Pool Fork/Join (Schéma Map/Reduce)

+

La classe ForkJoinPool est un exécuteur dont l'ordonnancement est adapté à une parallélisation selon + le schéma fork/join (voir cours, planches 43-45). Le principe (récursif) est

+
    +
  • de traiter directement (séquentiellement) un problème si sa taille est suffisamment petite
  • +
  • sinon, de diviser le problème en sous-problèmes, qui seront traités en parallèle (fork) et dont + les résultats seront attendus (join) et agrégés.
  • +
+

Ce schéma de programmation permet de créer dynamiquement un nombre de tâches adapté à la taille de chacun des + (sous)-problèmes rencontrés, chacune des tâches créées représentant une charge de travail équivalente. Ce schéma + est donc bien adapté au traitement de problèmes irréguliers, de grande taille. L'ordonnanceur de la classe + ForkJoinPool comporte en outre une régulation (vol de tâches) qui permet l'adaptation de + l'exécution aux capacités de calcul disponibles.

+

Il est important de noter que ce schéma repose sur le fait que les sous-tâches créées s'exécutent en parallèle, + et donc sur l'hypothèse qu'elles sont complètement indépendantes. Tout conflit d'accès aux ressources, ou + synchronisation compromet l'efficacité de ce schéma. Le schéma Fork/Join est donc idéalement et + principalement destiné aux calculs intensifs, irréguliers, en mémoire pure (sans E/S). Avec ce + schéma, les interactions et synchronisations entre tâches sont alors limitées aux interactions entre une tâche + mère et ses tâches filles, lorsque celles-ci ont terminé, et que la tâche mêre récupère les résultats des tâches + filles pour les agréger.

+
Classes et méthodes utiles
+
    +
  • ForkJoinPool: classe définissant l'exécuteur. Une instance de ForkJoinPool doit + être créée une fois et une seule pour toute la durée de l'application (ce n'est pas obligatoire, mais c'est + vivement conseillé, même pour les experts).
  • +
  • RecursiveTask<V> : définit une tâche soumise à l'exécuteur, fournissant un résultat
  • +
  • RecursiveAction : définit une tâche soumise à l'exécuteur, ne fournissant pas de résultat
  • +
  • ForkJoinTask<V> : superclasse de RecursiveTask and RecursiveAction, définissant la + plupart des méthodes utiles, comme fork() et join().
  • +
+
Un exemple
+

(fourni également dans l'archive jointe) réalise le schéma fork/join et illustre l'utilisation des principales + classes et méthodes dans ce cadre. Dans cette application, les données à traiter sont représentées par un simple + entier, qui symbolise leur volume.

+
import java.util.concurrent.RecursiveTask;
+import java.util.concurrent.ForkJoinPool;
+
+class TraiterProblème extends RecursiveTask<Integer> {
+
+ private int resteAFaire = 0;
+ private int résultat = 0;
+ static final int SEUIL = 10;
+
+ TraiterProblème(int resteAFaire) {
+    this.resteAFaire = resteAFaire;
+}
+
+ protected Integer compute() {
+
+    //si la tâche est trop grosse, on la décompose en 2
+    if(this.resteAFaire > SEUIL) {
+        System.out.println("Décomposition de resteAFaire : " + this.resteAFaire);
+
+        TraiterProblème sp1 = new TraiterProblème(this.resteAFaire / 2);
+        TraiterProblème sp2 = new TraiterProblème(this.resteAFaire / 2);
+
+        sp1.fork();
+        sp2.fork();
+
+        résultat = sp1.join()+ sp2.join();
+
+        return résultat;
+
+    } else {
+        System.out.println("Traitement direct de resteAFaire : " + this.resteAFaire);
+        return resteAFaire * 3;
+    }
+ }
+}
+
+public class FJG {
+ static ForkJoinPool fjp = new ForkJoinPool();
+ static final int TAILLE = 1024; //Attention : nécessairement une puissance de 2
+
+ public static void main(String[] args) throws Exception {
+    TraiterProblème monProblème = new TraiterProblème(TAILLE);
+    int résultat = fjp.invoke(monProblème);
+    System.out.println("Résultat final = " + résultat);
+ }
+}
+
Commentaires
+
    +
  • la méthode abstraite compute() définie dans RecursiveTask et + RecursiveAction contient le code du calcul récursif proprement dit. C'est l'analogue de la + méthode run() pour la classe Runnnable ou de la méthode call() pour + la classe Callable.
  • +
  • SEUIL est la taille de problème à partir duquel le travail n'est plus subdivisé. Ainsi + qu'indiqué précédemment sa valeur est un compromis, dépendant de la nature du problème. Une règle empirique + est que le nombre de sous-tâches créées devrait être compris entre 100 et 10 000. Il faut aussi savoir que + le pool ne peut comporter plus de 32K ouvriers.
  • +
  • le ForkJoinPool doit être créé une fois et une seule pour toute la durée de l'application (ce + n'est pas obligatoire, mais c'est conseillé, même pour les experts). La méthode employée ici pour créer ce + pool est celle nécessaire en Java 6 et 7. A partir de Java 8, cette création est inutile, car la classe + ForkJoinPool dispose d'un d'un pool par défaut (attribut de classe), dont la référence peut + être obtenue par appel de la méthode de classe ForkJoinPool.commonPool(). L'archive contient + une variante (FJGPlus) de l'application FJG, qui utilise cette facilité.
  • +
  • l'appel fjp.invoke(monProblème); permet de soumettre la tâche racine au pool.
  • +
+
Quelques écueils
+
    +
  • +

    l'implémentation actuelle de ForkJoinPool est d'autant moins efficace que les tâches sont + nombreuses. Ainsi, l'implémentation suivante de la branche if de la méthode + compute précédente aurait été sensiblement plus efficace (mais moins naturelle) :

    +
     if(this.resteAFaire > SEUIL) {
    +    System.out.println("Décomposition de resteAFaire : " + this.resteAFaire);
    +
    +    TraiterProblème sp1 = new TraiterProblème(this.resteAFaire / 2);
    +    TraiterProblème sp2 = new TraiterProblème(this.resteAFaire / 2);
    +    sp1.fork();
    +    résultat = sp2.compute();
    +    résultat = sp1.join()+résultat
    +    return résultat;
    +}
    +
  • +
  • +

    il ne faut pas oublier que join() est bloquant. Ainsi l'échange des appels à + join() et compute() dans la variante précédente aurait pour effet d'aboutir à + un programme séquentiel...

    +
  • +
+

Exercices

+

Vous aurez vraisemblablement besoin pour cette partie de deux méthodes de classe de la classe System + : System.nanoTime() et System.currentTimeMillis() qui fournissent une durée écoulée + (en ns et ms) depuis une date d'origine non spécifiée. La différence entre les valeurs retournées par deux + appels successifs permet d'évaluer le temps écoulé entre ces deux appels.

+

L'archive fournie propose différents exercices.
+ Chaque exercice comporte un calcul séquentiel (itératif ou récursif), qu'il faut paralléliser en utilisant un + pool fixe et/ou un pool Fork/Join.
+ Chaque exercice comporte une méthode main permettant de lancer et comparer les différentes versions. Des + commentaires // ********* A compléter ou // ********* A corriger signalent les (seuls) + endroits du code où vous devez intervenir pour implanter les versions parallèles du calcul séquentiel fourni. +

+

Les exercices utilisent des tableaux d'entiers stockés sur disque.
+ L'archive fournie comporte une application GCVT.java qui propose une classe TableauxDisque + permettant de générer, charger en mémoire, sauvegarder ou comparer de tels tableaux.
+ La méthode mainde l'application GCVT.java permet en outre d'appeler les méthodes de la classe + TableauxDisque depuis la console.
+ Cette application pourra en particulier être utilisée pour générer les jeux de données utiles aux + tests. En effet, pour que le gain apporté par les versions parallèles soit sensible, il est nécessaire + que les volumes de données traités soient significatifs, ce qui implique ici de travailler (pour l'évaluation de + performances) sur des tableaux de 1 à 100 millions d'entrées, ce qui aurait alourdi inutilement l'archive. Vous + devrez donc générer vos jeux de données avec cette application, sans oublier de supprimer les fichiers créés + une fois le TP passé, sans quoi vous risquez d'épuiser votre quota d'espace disque :) +

+

Les exercices peuvent être traités dans l'ordre suivant :

+
    +
  • Calcul du maximum d'un tableau (répertoire max). Le calcul d'un opérateur associatif et + commutatif sur un ensemble de données est une application canonique de la parallélisation. Cet exercice + permet de mettre simplement et directement en pratique les deux schémas (pool fixe et map/reduce) présentés + dans l'énoncé. +
      +
    • Le calcul séquentiel à paralléliser est une itération. A votre avis, quel sera le schéma de + parallélisation le plus naturel ? le plus efficace ?
    • +
    • Notez que le calcul étant très simple, il est important pour évaluer les performances de cet + exercice de travailler avec un grand tableau.
    • +
    • Comparer les deux versions (pool fixe et Fork/join) avec la version séquentielle. Les mesures + confirment-elles vos a priori ? Commentez.
    • +
    +
  • +
  • Tri d'un tableau selon le schéma tri-fusion (répertoire tri fusion). Même s'il est régulier, le + schéma récursif le prête parfaitement à l'utilisation du schéma map/reduce, et d'autant mieux qu'il est + organisé en 2 phases (tri, puis fusion). +
      +
    • Paralléliser l'algorithme récursif proposé en utilisant les deux schémas (pool fixe et Fork/Join) +
    • +
    • Comparer ces deux versions avec la version séquentielle, en termes de facilité de conception, et de + performances. Pour cet exercice, un tableau d'un million d'entrées devrait suffire.
    • +
    +
  • +
  • Pour aller plus loin (non demandé pour ce TP), l'application de comptage de mots dans un + répertoire (répertoire comptage des mots d'un répertoire) réalise la commande + find repertoire -exec grep mot {}\; Elle permet d'illustrer la parallélisation d'un problème + irrégulier. +
      +
    • Paralléliser l'algorithme récursif proposé en utilisant le schéma fork/join
    • +
    • Comparer cette version avec la version séquentielle, en termes de facilité de conception, et de + performances. Pour le test, on pourra prendre un répertoire contenant des fichiers sources, et + rechercher un mot clé du langage.
    • +
    +
  • +
+

Tester les + performances d'applications concurrentes en Java : quelques remarques pratiques

+
    +
  • sources de perturbation : cache, compilateur à la volée, ramasse miettes et optimiseur, charge de + l'environnement (matériel, réseau) -> répéter les mesures et retenir la meilleure
  • +
  • tester sur des volumes de données significatifs
  • +
  • connaître le nombre de processeurs réels disponibles
  • +
  • éviter les optimisations sauvages +
      +
    • avoir des tâches suffisamment complexes
      +
    • +
    • avoir un jeu de données varié (non constant en valeur et dans le temps)
    • +
    +
  • +
  • arrêter la décomposition en sous tâches à un seuil raisonnable
  • +
+ + + \ No newline at end of file diff --git a/TP5/README.md b/TP5/README.md new file mode 100644 index 0000000..cbb1dfc --- /dev/null +++ b/TP5/README.md @@ -0,0 +1,398 @@ +# Parallélisme régulé + +## Objectifs + +- gérer le parallélisme à gros grain +- paralléliser un algorithme par décomposition en sous-tâches +- connaître les services d'exécution de la plateforme Java + +## Prérequis + +Vous devez savoir parfaitement comment définir une activité (Thread) en Java, +comment lancer une activité, et comment attendre sa terminaison. + +** Si ce n'est pas le cas, +(re)voyez et (re)faites le travail demandé à la rubrique « concurrence et cohérence » avant +d'entamer ce TP** + +Vous aurez vraisemblablement besoin lors de ce TP d'utiliser les méthodes de classe suivantes +de la classe `Thread` : + +- `static Thread currentThread()` qui fournit la référence du thread en cours d'exécution +- `static void sleep(long ms) throws InterruptedException` qui suspend le thread + appelant pour une durée de `ms` millisecondes + +Enfin, vous aurez sans doute aussi besoin de deux méthodes de classe de la classe `System` : +`System.nanoTime()` et `System.currentTimeMillis()` qui fournissent une durée écoulée +(en ns et ms) depuis une date d'origine non spécifiée. La différence entre les valeurs +retournées par deux appels successifs permet d'évaluer le temps écoulé entre ces deux appels. + +## Préparation : services de régulation des activités en Java + +_La rapide présentation qui suit peut être complétée par la lecture de la partie +correspondante du cours sur les processus légers (planches 34-45) pour les notions et sur la +[documentation Java en ligne](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/package-summary.html) +pour la syntaxe et les détails techniques._ + +Les classes et notions utilisées jusqu'ici étaient destinées à définir et gérer la concurrence +explicitement, et à un niveau fin : le choix de lancer, d'attendre et de terminer une tâche +appartient entièrement au programmeur. De même, le programmeur a la charge des choix en +termes de gestion de la cohérence (variables `volatile`, classes atomiques...) et du type +d'attente (blocs `synchronized`, verrous, attente active). + +La plateforme Java fournit dans ses dernières versions la classe `Executor`, destinée à +séparer la gestion des activités des aspects purement applicatifs. Le principe est qu'un +objet de la classe `Executor` (« exécuteur ») fournit un _service_ de gestion et d'ordonnancement +d'activités, auquel on soumet des _tâches_ à traiter. Une application est donc vue comme +un ensemble de tâches qui sont fournies à l'exécuteur. L'exécuteur gère alors l'exécution +des tâches qui lui sont soumises de manière indépendante et transparente pour l'application. +L'objectif de ce service est de permettre + +- de simplifier la tâche du programmeur, puisqu'il n'a plus à gérer le démarrage des activités, + ni leur ordonnancement +- d'adapter le nombre d'activités exécutées à la charge et au nombre de processeurs + physiques disponibles + +Le paquetage `java.util.concurrent` définit 3 interfaces pour les exécuteurs : + +- `Executor`, qui fournit une méthode `execute`, permettant de soumettre une tâche `Runnable`. +- `ExecutorService`, qui étend `Executor`, avec une méthode `submit`, permettant de + soumettre une tâche `Callable` et renvoyant un objet `Future`, lequel permet de récupérer + la valeur de retour de la tâche `Callable` soumise. Un `ExecutorService` permet en outre + de soumettre des ensembles de tâches `Callable`, et de gérer la terminaison de l'exécuteur. +- `ScheduledExecutorService`, qui étend `ExecutorService` avec des méthodes permettant de + spécifier l'ordonnancement des tâches soumises. + +Le paquetage `java.util.concurrent` fournit différentes implémentations d'exécuteurs. Le +principe commun aux exécuteurs est de distribuer les tâches soumises à un ensemble +d'ouvriers. Chaque ouvrier est un thread cyclique, qui traite une par une les tâches qui +lui sont attribuées. + +Les exécuteurs fournis par le paquetage `java.util.concurrent` sont de deux sortes : + +### Pools de threads + +La classe `java.util.concurrent.Executors` fournit des méthodes permettant de créer des +pools de threads implantant `ExecutorService` avec un nombre d'ouvriers fixe +-- méthode `newFixedThreadPool` --, variable (adaptable) -- méthode `newCachedThreadPool`) ou +permettant une régulation par vol de tâches (voir cours) (méthode `newWorkStealingPool`). +Une variante implantant `ScheduledExecutorService` est proposée pour chacune de ces +méthodes, afin de permettre d'intervenir sur l'ordonnancement des tâches. Enfin, les classes +`java.util.concurrent.ThreadPoolExecutor` et `java.util.concurrent.ScheduledThreadPoolExecutor` +proposent encore davantage d'options sur la paramétrage et la supervision de l'ordonnancement. + +Les pools de threads évitent la création de nouveaux threads pour chaque tâche à traiter, +puisque qu'un même ouvrier est réutilisé pour traiter une suite de tâches, ce qui présente +plusieurs avantages : + +- éviter la création de threads apporte un gain (significatif lorsque les tâches sont nombreuses) + en termes de consommation de ressources mémoire et processeur, +- le délai de prise en charge des requêtes est réduit du temps de la création du traitant + de la requête, +- enfin, et surtout, le contrôle du nombre d'ouvriers va permettre de réguler et d'adapter + l'exécution en fonction des ressources matérielles disponibles, au lieu d'avoir une exécution + directement dépendante du flux de tâches à traiter. Ainsi, dans le cas d'un flux de tâches + augmentant brutalement (comme dans le cas d'une attaque par déni de service), les performances + se dégraderont progressivement (car le délai de prise en charge augmentera proportionnellement + au nombre de tâches en attente), mais il n'y aura pas d'écroulement dû à un épuisement des + ressources nécessaires. + +D'une manière générale, + +- Le choix ou l'adaptation du nombre d'ouvriers en fonction du nombre de processeurs + effectivement disponibles et de la charge courante est un élément clé de la + parallélisation avec un pool de threads : trop peu nombreux, les ouvriers ne pourront + exploiter tous les processeurs ; trop nombreux, il mobiliseront des ressources inutilement + et auront un impact négatif sur les performances. - _Note : l'appel de la méthode `Runtime.getRuntime().availableProcessors()` fournit le + nombre de processeurs disponibles pour la JVM courante._ +- Les pools de threads sont bien adaptés au traitement de problèmes + réguliers, c'est à dire aux problèmes décomposables en sous-problèmes de « taille » + équivalente, ce qui garantit une bonne répartition des tâches entre ouvriers. + +##### Classes et méthodes utiles + +- la classe `java.util.concurrent.Executors`, permet de créer des pools de threads par + appel de `newFixedThreadPool()` ou `newCachedThreadPool()` (cf supra) +- la classe `ExecutorService` et sa superclasse `Executor`, définissent l'interface + d'un exécuteur, avec notamment les méthodes `submit()`, `execute()` (cf supra) et + `shutdown()` (gestion de la terminaison de l'exécuteur) +- la classe `Future` fournit (immédiatement) une référence vers le résultat (à venir) + d'une tâche `Callable`soumise à l'exécuteur par `submit()`. L'appel de la méthode `get()` + permet d'obtenir le résultat effectif, en attendant s'il n'est pas encore disponible. +- les tâches ne renvoyant pas de résultat sont des `Runnable`, soumises à l'exécuteur par + `execute()`. + +##### Un exemple + + import java.util.concurrent.Future; + import java.util.concurrent.Callable; + import java.util.concurrent.Executors; + import java.util.concurrent.ExecutorService; + + class SigmaC implements Callable { + private long début; + private long fin; + + SigmaC(long d, long f) { début = d; fin = f;} + + @Override + public Long call() { // le résultat doit être un objet + long s = 0; + for (long i = début; i <= fin; i++) s = s + i; + return s; + } + } + + class SigmaR implements Runnable { + private long début; + private long fin; + + SigmaR(long d, long f) { début = d; fin = f;} + + @Override + public void run() { + long s = 0; + for (long i = début; i <= fin; i++) s = s + i; + System.out.println("Calcul terminé. ∑("+début+","+fin+") = "+s); + } + } + + public class Somme { + public static void main(String[] args) throws Exception { + + ExecutorService poule = Executors.newFixedThreadPool(2); + + Future f1 = poule.submit(new SigmaC(0L,1_000_000_000L)); + Future f2 = poule.submit(new SigmaC(0L,4_000_000_000L)); + poule.execute(new SigmaR(900_000L,1_000_000_000L)); + Future f3 = poule.submit(new SigmaC(1,100)); + Future f4 = poule.submit(new SigmaC(0L,3_000_000_000L)); + + poule.shutdown(); + + System.out.println("Résultat obtenu. f1 = "+f1.get()); + System.out.println("Résultat obtenu. f2 = "+f2.get()); + System.out.println("Résultat obtenu. f3 = "+f3.get()); + System.out.println("Résultat obtenu. f4 = "+f4.get()); + } + } + +##### Commentaires + +- L'application crée un pool avec un nombre fixe d'ouvriers (2), puis lance 5 tâches : les + deux premières et les deux dernières soumises (`Callable` , soumises par appel à `submit()`) + rendent un résultat `Future`, récupéré de manière bloquante par l'appel à la + méthode `get()`. La troisième (`Runnable`, soumise par appel à `execute()`) s'exécute de + manière asynchrone. +- L'exécution voit la tâche `Runnable` terminer après la première soumise (`f1`), + car bien que plus courte, elle ne peut démarrer tant que l'une des deux premières tâches lancées + n'est pas terminée, la taille du pool étant de 2. L'appel `f2.get()` entraîne l'attente de + la terminaison de `f2`, plus longue que `f1`et la tâche `Runnable` cumulées. L'appel de + `f3.get()` retourne immédiatement, car `f3`, courte est déjà terminé. L'appel `f4.get()` + entraîne l'attente de la terminaison de `f4`. +- `shutdown` permet de terminer proprement l'exécuteur, qui dès lors n'accepte plus de + nouvelles tâches. L'application Java termine avec la dernière tâche traitée. Si `shutdown` + est omis, l'application ne peut terminer, car les threads de l'exécuteur restent + en attente de nouvelles tâches. +- L'archive contient une variante (`SommePlus`) de l'application `Somme`, qui illustre + l'utilisation de : + _ `invokeAll()` sur une collection de tâches/actions pour soumettre une collection + (ici une liste) de `Callable`. Les résultats sont alors rendus dans une liste de `Future`; + _ `get()` sur les `Future` de cette liste, pour récupérer les résultats effectifs + +### Pool Fork/Join (Schéma Map/Reduce) + +La classe `ForkJoinPool` est un exécuteur dont l'ordonnancement est adapté à une +parallélisation selon le schéma _fork/join_ (voir cours, planches 43-45). +Le principe (récursif) est + +- de traiter directement (séquentiellement) un problème si sa taille est suffisamment petite +- sinon, de diviser le problème en sous-problèmes, qui seront traités en parallèle (`fork`) + et dont les résultats seront attendus (`join`) et agrégés. + +Ce schéma de programmation permet de créer dynamiquement un nombre de tâches adapté à la +taille de chacun des (sous)-problèmes rencontrés, chacune des tâches créées représentant +une charge de travail équivalente. Ce schéma est donc bien adapté au traitement de problèmes +irréguliers, de grande taille. L'ordonnanceur de la classe `ForkJoinPool` comporte en outre +une régulation (vol de tâches) qui permet l'adaptation de l'exécution aux capacités de calcul +disponibles. + +Il est important de noter que ce schéma repose sur le fait que les sous-tâches créées +s'exécutent en parallèle, et donc sur l'hypothèse qu'elles sont complètement indépendantes. +Tout conflit d'accès aux ressources, ou synchronisation compromet l'efficacité de ce schéma. +**Le schéma Fork/Join est donc idéalement et principalement destiné aux calculs intensifs, +irréguliers, en mémoire pure (sans E/S)**. Avec ce schéma, les interactions et synchronisations +entre tâches sont alors limitées aux interactions entre une tâche mère et ses tâches +filles, lorsque celles-ci ont terminé, et que la tâche mêre récupère les résultats des +tâches filles pour les agréger. + +##### Classes et méthodes utiles + +- `ForkJoinPool`: classe définissant l'exécuteur. Une instance de `ForkJoinPool` doit être + créée une fois et une seule pour toute la durée de l'application (ce n'est pas + obligatoire, mais c'est vivement conseillé, même pour les experts). +- `RecursiveTask` : définit une tâche soumise à l'exécuteur, fournissant un résultat +- `RecursiveAction` : définit une tâche soumise à l'exécuteur, ne fournissant pas de résultat +- `ForkJoinTask` : superclasse de RecursiveTask and RecursiveAction, définissant la + plupart des méthodes utiles, comme `fork()` et `join()`. + +##### Un exemple + +(fourni également dans l'archive jointe) réalise le schéma fork/join et illustre +l'utilisation des principales classes et méthodes dans ce cadre. Dans cette application, +les données à traiter sont représentées par un simple entier, qui symbolise leur volume. + + import java.util.concurrent.RecursiveTask; + import java.util.concurrent.ForkJoinPool; + + class TraiterProblème extends RecursiveTask { + + private int resteAFaire = 0; + private int résultat = 0; + static final int SEUIL = 10; + + TraiterProblème(int resteAFaire) { + this.resteAFaire = resteAFaire; + } + + protected Integer compute() { + + //si la tâche est trop grosse, on la décompose en 2 + if(this.resteAFaire > SEUIL) { + System.out.println("Décomposition de resteAFaire : " + this.resteAFaire); + + TraiterProblème sp1 = new TraiterProblème(this.resteAFaire / 2); + TraiterProblème sp2 = new TraiterProblème(this.resteAFaire / 2); + + sp1.fork(); + sp2.fork(); + + résultat = sp1.join()+ sp2.join(); + + return résultat; + + } else { + System.out.println("Traitement direct de resteAFaire : " + this.resteAFaire); + return resteAFaire * 3; + } + } + } + + public class FJG { + static ForkJoinPool fjp = new ForkJoinPool(); + static final int TAILLE = 1024; //Attention : nécessairement une puissance de 2 + + public static void main(String[] args) throws Exception { + TraiterProblème monProblème = new TraiterProblème(TAILLE); + int résultat = fjp.invoke(monProblème); + System.out.println("Résultat final = " + résultat); + } + } + +##### Commentaires + +- la méthode abstraite `compute()` définie dans `RecursiveTask` et `RecursiveAction` + contient le code du calcul récursif proprement dit. C'est l'analogue de la méthode `run()` + pour la classe `Runnnable` ou de la méthode `call()` pour la classe `Callable`. +- `SEUIL` est la taille de problème à partir duquel le travail n'est plus subdivisé. Ainsi + qu'indiqué précédemment sa valeur est un compromis, dépendant de la nature du problème. Une + règle empirique est que le nombre de sous-tâches créées devrait être compris entre 100 et + 10 000. Il faut aussi savoir que le pool ne peut comporter plus de 32K ouvriers. +- le `ForkJoinPool` doit être créé une fois et une seule pour toute la durée de + l'application (ce n'est pas obligatoire, mais c'est conseillé, même pour les experts). + La méthode employée ici pour créer ce pool est celle nécessaire en Java 6 et 7. + A partir de Java 8, cette création est inutile, car la classe `ForkJoinPool` dispose d'un + d'un pool par défaut (attribut de classe), dont la référence peut être obtenue par appel + de la méthode de classe `ForkJoinPool.commonPool()`. L'archive contient + une variante (`FJGPlus`) de l'application `FJG`, qui utilise cette facilité. +- l'appel `fjp.invoke(monProblème);` permet de soumettre la tâche racine au pool. + +##### Quelques écueils + +- l'implémentation actuelle de `ForkJoinPool` est d'autant moins efficace que les tâches + sont nombreuses. Ainsi, l'implémentation suivante de la branche `if` de la méthode `compute` + précédente aurait été sensiblement plus efficace (mais moins naturelle) : + + if(this.resteAFaire > SEUIL) { + System.out.println("Décomposition de resteAFaire : " + this.resteAFaire); + + TraiterProblème sp1 = new TraiterProblème(this.resteAFaire / 2); + TraiterProblème sp2 = new TraiterProblème(this.resteAFaire / 2); + sp1.fork(); + résultat = sp2.compute(); + résultat = sp1.join()+résultat + return résultat; + } + +- il ne faut pas oublier que `join()` est bloquant. Ainsi l'échange des appels à `join()` et + `compute()` dans la variante précédente aurait pour effet d'aboutir à un programme séquentiel... + +## Exercices + +Vous aurez vraisemblablement besoin pour cette partie de deux méthodes de classe de la classe `System` : +`System.nanoTime()` et `System.currentTimeMillis()` qui fournissent une durée écoulée +(en ns et ms) depuis une date d'origine non spécifiée. La différence entre les valeurs +retournées par deux appels successifs permet d'évaluer le temps écoulé entre ces deux appels. + +L'archive fournie propose différents exercices. +Chaque exercice comporte un calcul +séquentiel (itératif ou récursif), qu'il faut paralléliser en utilisant un pool fixe et/ou +un pool Fork/Join. +Chaque exercice comporte une méthode main permettant de lancer et +comparer les différentes versions. Des commentaires `// ********* A compléter` ou +`// ********* A corriger` signalent les (seuls) endroits du code où vous devez intervenir +pour implanter les versions parallèles du calcul séquentiel fourni. + +Les exercices utilisent des tableaux d'entiers stockés sur disque. +L'archive fournie comporte une application GCVT.java qui propose une classe +`TableauxDisque` permettant de générer, charger +en mémoire, sauvegarder ou comparer de tels tableaux. +La méthode `main`de l'application +GCVT.java permet en outre d'appeler les méthodes de la classe `TableauxDisque` depuis +la console. +_Cette application pourra en particulier être utilisée pour générer les jeux de +données utiles aux tests._ En effet, pour que le gain apporté par les versions parallèles +soit sensible, il est nécessaire que les volumes de données traités soient significatifs, +ce qui implique ici de travailler (pour l'évaluation de performances) sur des tableaux +de 1 à 100 millions d'entrées, ce qui aurait alourdi inutilement l'archive. Vous devrez donc +générer vos jeux de données avec cette application, _sans oublier de supprimer les fichiers +créés une fois le TP passé_, sans quoi vous risquez d'épuiser votre quota d'espace disque :) + +Les exercices peuvent être traités dans l'ordre suivant : + +- Calcul du maximum d'un tableau (répertoire `max`). Le calcul d'un opérateur associatif + et commutatif sur un ensemble de données est une application canonique de la parallélisation. + Cet exercice permet de mettre simplement et directement en pratique les deux schémas + (pool fixe et map/reduce) présentés dans l'énoncé. + _ Le calcul séquentiel à paralléliser est une itération. A votre avis, quel sera + le schéma de parallélisation le plus naturel ? le plus efficace ? + _ Notez que le calcul étant très simple, il est important pour évaluer les performances + de cet exercice de travailler avec un grand tableau. \* Comparer les deux versions (pool fixe et Fork/join) avec la version séquentielle. + Les mesures confirment-elles vos a priori ? Commentez. +- Tri d'un tableau selon le schéma tri-fusion (répertoire `tri fusion`). Même s'il est régulier, + le schéma récursif le prête parfaitement à l'utilisation du schéma map/reduce, et d'autant + mieux qu'il est organisé en 2 phases (tri, puis fusion). + _ Paralléliser l'algorithme récursif proposé en utilisant les deux schémas (pool fixe + et Fork/Join) + _ Comparer ces deux versions avec la version séquentielle, en termes de facilité de + conception, et de performances. Pour cet exercice, un tableau d'un million d'entrées + devrait suffire. +- **Pour aller plus loin** (non demandé pour ce TP), l'application de comptage de mots dans un répertoire + (répertoire `comptage des mots d'un répertoire`) réalise la commande + `find repertoire -exec grep mot {}\;` + Elle permet d'illustrer la parallélisation d'un problème irrégulier. + _ Paralléliser l'algorithme récursif proposé en utilisant le schéma fork/join + _ Comparer cette version avec la version séquentielle, en termes de facilité de + conception, et de performances. Pour le test, on pourra prendre un répertoire contenant + des fichiers sources, et rechercher un mot clé du langage. + +## Tester les performances d'applications concurrentes en Java : quelques remarques pratiques + +- sources de perturbation : cache, compilateur à la volée, ramasse miettes et optimiseur, + charge de l'environnement (matériel, réseau) + -> répéter les mesures et retenir la meilleure +- tester sur des volumes de données significatifs +- connaître le nombre de processeurs réels disponibles +- éviter les optimisations sauvages - avoir des tâches suffisamment complexes + - avoir un jeu de données varié (non constant en valeur et dans le temps) +- arrêter la décomposition en sous tâches à un seuil raisonnable diff --git a/TP5/comptage des mots d'un répertoire/Comptage.java b/TP5/comptage des mots d'un répertoire/Comptage.java new file mode 100644 index 0000000..6b42d0f --- /dev/null +++ b/TP5/comptage des mots d'un répertoire/Comptage.java @@ -0,0 +1,163 @@ + +/* v0.0 9/10/16 (PM). + * Comptage + * Compte le nombre d'occurrences de dans l'ensemble des documents + * appartenant à la sous arborescence dont est racine. + * Les liens ne sont pas suivis. + */ +import java.nio.file.Paths; +import java.nio.file.attribute.FileOwnerAttributeView; +import java.nio.file.Path; +import java.nio.file.DirectoryStream; +import java.nio.file.Files; +import java.nio.file.LinkOption; +import java.io.IOException; +import java.io.BufferedReader; +import java.io.InputStreamReader; + +import java.util.concurrent.RecursiveTask; +import java.util.concurrent.ForkJoinPool; + +import java.util.List; +import java.util.LinkedList; + +import java.util.Scanner; +import java.util.regex.Pattern; +import java.util.regex.Matcher; +import java.util.NoSuchElementException; + +class CompterMots extends RecursiveTask { + private Path racine; + private String mot; + + CompterMots(Path p, String m) { + racine = p; + mot = m; + } + + @Override + protected Long compute() { + long cpt = 0L; + List filles = new LinkedList<>(); + DirectoryStream ls = null; + + if (Files.isRegularFile(racine, LinkOption.NOFOLLOW_LINKS)) { + cpt = Comptage.nbOccurrences(racine, mot); + } else if (Files.isDirectory(racine, LinkOption.NOFOLLOW_LINKS)) { + try { + ls = Files.newDirectoryStream(racine); + } catch (IOException iox) { + System.err.println("(mono)" + iox); + } + for (Path f : ls) { + filles.add(new CompterMots(f, mot)); + } + for (CompterMots fille : filles) { + fille.fork(); + } + for (CompterMots fille : filles) { + cpt += fille.join(); + } + } // Sinon (lien, fichier non régulier), on ne fait rien (cpt=0) + return cpt; + } +} + +public class Comptage { + + static Long nbOccurrences(Path p, String mot) { + long cpt = 0; + Scanner s = null; + Pattern ptn = Pattern.compile(mot); + Matcher m = null; + + try { + s = new Scanner(new InputStreamReader(Files.newInputStream(p))); + } catch (IOException iox) { + System.err.println("(nbOccurrences)" + iox); + } + + while (s.hasNextLine()) { + m = ptn.matcher(s.nextLine()); + while (m.find()) + cpt++; + } + s.close(); + return cpt; + } + + static Long comptageMono(Path p, String mot) { + long cpt = 0L; + DirectoryStream ls = null; + + if (Files.isRegularFile(p, LinkOption.NOFOLLOW_LINKS)) { + cpt = Comptage.nbOccurrences(p, mot); + } else if (Files.isDirectory(p, LinkOption.NOFOLLOW_LINKS)) { + try { + ls = Files.newDirectoryStream(p); + } catch (IOException iox) { + System.err.println("(mono)" + iox); + } + for (Path f : ls) { + cpt = cpt + comptageMono(f, mot); + } + } // Sinon (lien, fichier non régulier), on ne fait rien (cpt=0) + return cpt; + } + + static Long comptageFJ(ForkJoinPool f, Path p, String mot) { + CompterMots tout = new CompterMots(p, mot); + return tout.compute(); + } + + public static void main(String[] args) throws IOException { + + int nbEssais = 0; + long résultat = 0L; + long départ, fin; + String chemin = ""; + String motif = ""; + + long[] tempsMono, tempsForkJoin; + + if (args.length == 3) { // analyse des paramètres + chemin = args[0]; + motif = args[1]; + try { + nbEssais = Integer.parseInt(args[2]); + } catch (NumberFormatException nfx) { + throw new IllegalArgumentException("Usage : Comptage "); + } + } + if ((nbEssais < 1) || (!Files.isDirectory(Paths.get(chemin), LinkOption.NOFOLLOW_LINKS) + && !Files.isRegularFile(Paths.get(chemin), LinkOption.NOFOLLOW_LINKS))) + throw new IllegalArgumentException("Usage : Comptage "); + + // appel correct + tempsMono = new long[nbEssais]; + tempsForkJoin = new long[nbEssais]; + + // créer un pool ForkJoin + ForkJoinPool fjp = new ForkJoinPool(); + + for (int i = 0; i < nbEssais; i++) { + départ = System.nanoTime(); + résultat = comptageMono(Paths.get(chemin), motif); + fin = System.nanoTime(); + tempsMono[i] = (fin - départ); + System.out.println("Essai [" + i + "] : " + résultat + " occurrences de " + motif + ", durée (mono) " + + tempsMono[i] / 1_000 + "µs"); + } + System.out.println("--------------------"); + + for (int i = 0; i < nbEssais; i++) { + départ = System.nanoTime(); + résultat = comptageFJ(fjp, Paths.get(chemin), motif); + fin = System.nanoTime(); + tempsForkJoin[i] = (fin - départ); + System.out.println("Essai [" + i + "] : " + résultat + " occurrences de " + motif + ", durée (FJ) " + + tempsForkJoin[i] / 1_000 + "µs"); + } + System.out.println("--------------------"); + } +} \ No newline at end of file diff --git a/TP5/données/t1000 b/TP5/données/t1000 new file mode 100644 index 0000000..55aa5e9 Binary files /dev/null and b/TP5/données/t1000 differ diff --git a/TP5/exemples énoncé/FJG/FJG.java b/TP5/exemples énoncé/FJG/FJG.java new file mode 100644 index 0000000..aebf93c --- /dev/null +++ b/TP5/exemples énoncé/FJG/FJG.java @@ -0,0 +1,47 @@ +import java.util.concurrent.RecursiveTask; +import java.util.concurrent.ForkJoinPool; + + +class TraiterProblème extends RecursiveTask { + + private int resteAFaire = 0; + private int résultat = 0; + static final int SEUIL = 10; + + TraiterProblème(int resteAFaire) { + this.resteAFaire = resteAFaire; + } + + protected Integer compute() { + + //si la tâche est trop grosse, on la décompose en 2 + if(this.resteAFaire > SEUIL) { + System.out.println("Décomposition de resteAFaire : " + this.resteAFaire); + + TraiterProblème sp1 = new TraiterProblème(this.resteAFaire / 2); + TraiterProblème sp2 = new TraiterProblème(this.resteAFaire / 2); + + sp1.fork(); + sp2.fork(); + + résultat = sp1.join()+ sp2.join(); + + return résultat; + + } else { + System.out.println("Traitement direct de resteAFaire : " + this.resteAFaire); + return 10 * resteAFaire; + } + } +} + +public class FJG { + static ForkJoinPool fjp = new ForkJoinPool(); + static final int TAILLE = 1024; //Attention : nécessairement une puissance de 2 + + public static void main(String[] args) throws Exception { + TraiterProblème monProblème = new TraiterProblème(TAILLE); + int résultat = fjp.invoke(monProblème); + System.out.println("Résultat final = " + résultat); + } +} \ No newline at end of file diff --git a/TP5/exemples énoncé/FJG/FJGPlus.java b/TP5/exemples énoncé/FJG/FJGPlus.java new file mode 100644 index 0000000..83b3138 --- /dev/null +++ b/TP5/exemples énoncé/FJG/FJGPlus.java @@ -0,0 +1,57 @@ +import java.util.concurrent.RecursiveTask; +import java.util.concurrent.ForkJoinPool; + + +class TraiterProblème extends RecursiveTask { + + private int resteAFaire = 0; + private int résultat = 0; + static final int SEUIL = 10; + + TraiterProblème(int resteAFaire) { + this.resteAFaire = resteAFaire; + } + + protected Integer compute() { + + //si la tâche est trop grosse, on la décompose en 2 + if(this.resteAFaire > SEUIL) { + System.out.println("Décomposition de resteAFaire : " + this.resteAFaire); + + TraiterProblème sp1 = new TraiterProblème(this.resteAFaire / 2); + TraiterProblème sp2 = new TraiterProblème(this.resteAFaire / 2); + + sp1.fork(); + résultat = sp2.compute(); + résultat = sp1.join()+résultat; + return résultat; + + } else { + System.out.println("Traitement direct de resteAFaire : " + this.resteAFaire); + try { + Thread.sleep(100,0); + } + catch (InterruptedException ie) + { + System.out.println("InterruptedException : " + ie); + } + return 10 * resteAFaire; + } + } +} + +public class FJGPlus { + static final int TAILLE = 1024; //Pour cet exemple, nécessairement une puissance de 2 + + public static void main(String[] args) throws Exception { + long fin,départ; + TraiterProblème monProblème = new TraiterProblème(TAILLE); + + départ = System.nanoTime(); + int résultat = ForkJoinPool.commonPool().invoke(monProblème); + fin = System.nanoTime(); + System.out.println("Résultat final = " + résultat); + System.out.println("Durée exécution (ms): " + (fin-départ)/1000000L); + System.out.println(); + } +} \ No newline at end of file diff --git a/TP5/exemples énoncé/pool fixe/Somme.java b/TP5/exemples énoncé/pool fixe/Somme.java new file mode 100644 index 0000000..f316124 --- /dev/null +++ b/TP5/exemples énoncé/pool fixe/Somme.java @@ -0,0 +1,62 @@ +import java.util.concurrent.Future; +import java.util.concurrent.Callable; +import java.util.concurrent.Executors; +import java.util.concurrent.ExecutorService; + +class SigmaC implements Callable { + private long début; + private long fin; + + SigmaC(long d, long f) { + début = d; + fin = f; + } + + @Override + public Long call() { // le résultat doit être un objet + long s = 0; + for (long i = début; i <= fin; i++) { + s = s + i; + } + return s; + } +} + +class SigmaR implements Runnable { + private long début; + private long fin; + + SigmaR(long d, long f) { + début = d; + fin = f; + } + + @Override + public void run() { + long s = 0; + for (long i = début; i <= fin; i++) { + s = s + i; + } + System.out.println("Calcul terminé. ∑("+début+","+fin+") = "+s); + } +} + +public class Somme { + public static void main(String[] args) throws Exception { + + ExecutorService poule = Executors.newFixedThreadPool(2); + + Future f1 = poule.submit(new SigmaC(0L,1_000_000_000L)); + Future f2 = poule.submit(new SigmaC(0L,4_000_000_000L)); + poule.execute(new SigmaR(900_000L,1_000_000_000L)); + Future f3 = poule.submit(new SigmaC(1,100)); + Future f4 = poule.submit(new SigmaC(0L,3_000_000_000L)); + + poule.shutdown(); + + System.out.println("Résultat obtenu. f1 = "+f1.get()); + System.out.println("Résultat obtenu. f2 = "+f2.get()); + System.out.println("Résultat obtenu. f3 = "+f3.get()); + System.out.println("Résultat obtenu. f4 = "+f4.get()); + } +} \ No newline at end of file diff --git a/TP5/exemples énoncé/pool fixe/SommePlus.java b/TP5/exemples énoncé/pool fixe/SommePlus.java new file mode 100644 index 0000000..98eadbc --- /dev/null +++ b/TP5/exemples énoncé/pool fixe/SommePlus.java @@ -0,0 +1,42 @@ +import java.util.List; +import java.util.concurrent.Future; +import java.util.concurrent.Callable; +import java.util.concurrent.Executors; +import java.util.concurrent.ExecutorService; + +import static java.util.Arrays.asList; + +public class SommePlus { + + static class Sum implements Callable { + private final long from; + private final long to; + + Sum(long from, long to) { + this.from = from; + this.to = to; + } + + @Override + public Long call() { + long acc = 0; + for (long i = from; i <= to; i++) { + acc = acc + i; + } + return acc; + } + } + + public static void main(String[] args) throws Exception { + + ExecutorService executor = Executors.newFixedThreadPool(2); + List> results = executor.invokeAll(asList( + new Sum(0, 10), new Sum(100, 1_000), new Sum(10_000, 1_000_000) + )); + executor.shutdown(); + + for (Future result : results) { + System.out.println(result.get()); + } + } +} diff --git a/TP5/max/GCVT.java b/TP5/max/GCVT.java new file mode 100644 index 0000000..5d90102 --- /dev/null +++ b/TP5/max/GCVT.java @@ -0,0 +1,176 @@ +//v0.1 15/11/20 (PM) (0.1 :clarification du message d'erreur principal) + +import java.io.ObjectOutputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.FileNotFoundException; +import java.nio.file.Paths; +import java.nio.file.Files; +import java.nio.file.LinkOption; +import java.io.ObjectOutputStream; +import java.io.FileOutputStream; +import java.io.FileInputStream; +import java.io.ObjectInputStream; +import java.util.Random; + +class TableauxDisque { + static boolean générer(String chemin, int taille, int max) throws IOException, FileNotFoundException { + /* génère un tableau de dimension taille, contenant des entiers aléatoires, compris entre 0 et max, + et enregistre ce tableau dans le fichier dont le chemin est fourni en argument */ + Random rdm = new Random(); + int [] tableau; + if ((taille < 1)||(max < 1)) { + System.err.println("Générer : paramètres inattendus (taille ou max < 1)"); + return false; + } else { + tableau = new int[taille]; + for (int i=0; i"+tableau[i]); + } + TableauxDisque.sauver(chemin,tableau); + System.out.println("Sauvé : "); + TableauxDisque.visualiser(chemin, 0, tableau.length); + break; + default: + throw new IllegalArgumentException( + "\nUsage : GCVT -g (pour générer un tableau" + +" de entiers inférieurs à )\n" + + " ou GCVT -c (pour comparer le contenu" + + " de 2 fichiers à partir de la position )\n" + + " ou GCVT -v (pour visualiser le contenu d'un" + + " fichier entre les positions et )\n" + + "Note : est un chemin, qui peut être relatif. Exemple ../data/t1000\n"); + } + } +} \ No newline at end of file diff --git a/TP5/max/MaxTab.java b/TP5/max/MaxTab.java new file mode 100644 index 0000000..2965a2e --- /dev/null +++ b/TP5/max/MaxTab.java @@ -0,0 +1,193 @@ +//v0.1.2 15/11/21 (PM) (0.1.2 : modification (mineure) des parties à compléter) + +import java.nio.file.Paths; +import java.nio.file.Files; +import java.nio.file.LinkOption; +import java.io.IOException; +import java.io.FileNotFoundException; +import java.util.concurrent.ExecutionException; + +import java.util.List; +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedList; + +import java.util.concurrent.Future; +import java.util.concurrent.Callable; +import java.util.concurrent.Executors; +import java.util.concurrent.ExecutorService; + +import java.util.concurrent.RecursiveTask; +import java.util.concurrent.ForkJoinPool; + +class MaxLocal implements Callable { + // pool fixe + private int début; + private int fin; + private int[] tableau; + + MaxLocal(int[] t, int d, int f) { + this.début = d; + this.fin = f; + this.tableau = t; + } + + @Override + public Integer call() { // le résultat doit être un objet + int ml = 0; + for (int i = début; i < fin; i++) { + if (tableau[i] > ml) { + ml = tableau[i]; + } + } + return ml; + } +} + +class TraiterFragment extends RecursiveTask { + // pool fork-join + private int début; + private int fin; + private int[] tableau; + static int seuil; + + private int max = 0; + + TraiterFragment(int[] t, int d, int f) { + début = d; + fin = f; + tableau = t; + } + + protected Integer compute() { + + int resteAFaire = this.fin - this.début; + + if (resteAFaire > TraiterFragment.seuil) { + // System.out.println("Décomposition de resteAFaire : " + resteAFaire); + + TraiterFragment sp1 = new TraiterFragment(tableau, début, début + resteAFaire / 2); + TraiterFragment sp2 = new TraiterFragment(tableau, début + resteAFaire / 2, fin); + + sp1.fork(); + sp2.fork(); + + return Math.max(sp1.join(), sp2.join()); + + } else { + // System.out.println("Traitement direct de resteAFaire : " + resteAFaire); + return new MaxLocal(tableau, début, fin).call(); + } + } +} + +public class MaxTab { + // --------- Mono + static int maxMono(int[] t) { + int max = 0; + for (int i = 0; i < t.length; i++) { + max = Math.max(t[i], max); + } + return max; + } + + // -------- Pool Fixe + static int maxPoolFixe(ExecutorService xs, int[] t, int nbT) throws InterruptedException, ExecutionException { + List> résultats = new LinkedList>(); + int max = 0; // on suppose que les entiers à trier sont positifs + int grain = Math.max(1, t.length / nbT); /* + * taille d'une recherche locale = taille du tableau/nombre de tâches + * soumises (ou 1 dans le cas (aberrant) où il y a plus de tâches que + * d'éléments du tableau) + */ + + return max; + } + + // -------- Pool ForkJoin + static int maxForkJoin(ForkJoinPool f, int[] t) { + TraiterFragment tout = new TraiterFragment(t, 0, t.length - 1); + return tout.compute(); + } + + // -------- main + public static void main(String[] args) + throws InterruptedException, ExecutionException, IOException, FileNotFoundException { + + int nbOuvriersPool = 0; // nb ouvriers du pool fixe. Bonne valeur : nb de processeurs disponibles + int nbEssais = -1; + int nbTâches = -1; + int tailleTronçon = -1; // nb d'éléments du tableau traités par chaque ouvrier (FJ) + String chemin = ""; + int[] tableau; + long départ, fin; + int max; + + long[] tempsMono, tempsPool, tempsForkJoin; + + if (args.length == 5) { // analyse des paramètres + chemin = args[0]; + try { + nbEssais = Integer.parseInt(args[1]); + nbTâches = Integer.parseInt(args[2]); + tailleTronçon = Integer.parseInt(args[3]); + nbOuvriersPool = Integer.parseInt(args[4]); + } catch (NumberFormatException nfx) { + throw new IllegalArgumentException("\nUsage : MaxTab " + + " " + " \n" + + " * = nb de fragments à traiter \n" + + " * = taille du fragment \n"); + } + } + if ((nbEssais < 1) || (nbTâches < 1) || (tailleTronçon < 1) || (nbOuvriersPool < 1) + || !Files.isRegularFile(Paths.get(chemin), LinkOption.NOFOLLOW_LINKS)) + throw new IllegalArgumentException( + "\nUsage : MaxTab " + " " + + " \n" + " * = nb de fragments à traiter \n" + + " * = taille du fragment \n"); + // appel correct + tempsMono = new long[nbEssais]; + tempsPool = new long[nbEssais]; + tempsForkJoin = new long[nbEssais]; + tableau = TableauxDisque.charger(chemin); + System.out.println(Runtime.getRuntime().availableProcessors() + " processeurs disponibles pour la JVM"); + + // créer un pool avec un nombre fixe d'ouvriers + ExecutorService poule = Executors.newFixedThreadPool(nbOuvriersPool); + + // créer un pool ForkJoin + ForkJoinPool fjp = new ForkJoinPool(); + TraiterFragment.seuil = tailleTronçon; + + for (int i = 0; i < nbEssais; i++) { + départ = System.nanoTime(); + max = maxMono(tableau); + fin = System.nanoTime(); + tempsMono[i] = (fin - départ); + System.out + .println("Essai [" + i + "] : maximum = " + max + ", durée (mono) " + tempsMono[i] / 1_000 + "µs"); + } + System.out.println("--------------------"); + + for (int i = 0; i < nbEssais; i++) { + départ = System.nanoTime(); + max = maxPoolFixe(poule, tableau, nbTâches); + fin = System.nanoTime(); + tempsPool[i] = (fin - départ); + System.out + .println("Essai [" + i + "] : maximum = " + max + ", durée (pool) " + tempsPool[i] / 1_000 + "µs"); + } + poule.shutdown(); + System.out.println("--------------------"); + + for (int i = 0; i < nbEssais; i++) { + départ = System.nanoTime(); + max = maxForkJoin(fjp, tableau); + fin = System.nanoTime(); + tempsForkJoin[i] = (fin - départ); + System.out.println( + "Essai [" + i + "] : maximum = " + max + ", durée (FJ) " + tempsForkJoin[i] / 1_000 + "µs"); + } + System.out.println("--------------------"); + } +} \ No newline at end of file diff --git a/TP5/max/array b/TP5/max/array new file mode 100644 index 0000000..bad6b1c Binary files /dev/null and b/TP5/max/array differ diff --git a/TP5/reponses.md b/TP5/reponses.md new file mode 100644 index 0000000..8f12180 --- /dev/null +++ b/TP5/reponses.md @@ -0,0 +1,21 @@ +## Exercice 1 + +### Le calcul séquentiel à paralléliser est une itération. A votre avis, quel sera le schéma de parallélisation le plus naturel ? le plus efficace ? + +La parllélisation avec un pool fixe est plus naturelle et semble plus efficace. + +### Notez que le calcul étant très simple, il est important pour évaluer les performances de cet exercice de travailler avec un grand tableau. Comparer les deux versions (pool fixe et Fork/join) avec la version séquentielle. Les mesures confirment-elles vos a priori ? Commentez. + +Sur de grands tableau, la parallélisation est nettement plus efficace que la version séquentielle. La version à pool fixe est plus rapide que celle du fork/join, puisque allouer des tableaux est couteux. + +## Exercice 2 + +### Paralléliser l’algorithme récursif proposé en utilisant les deux schémas (pool fixe et Fork/Join). Comparer ces deux versions avec la version séquentielle, en termes de facilité de conception, et de performances. + +La version avec pool fixe est plus rapide que la version séquentielle (pour de grands tableaux) et nettement plus rapide que la version fork/join. La version fork/join est tout de même plus simple à programmer. + +## Exercice 3 + +### Comparer cette version avec la version séquentielle, en termes de facilité de conception, et de performances. Pour le test, on pourra prendre un répertoire contenant des fichiers sources, et rechercher un mot clé du langage. + +Dans ce cas le fork/join est plus rapide que la version séquentielle. De le principe du fork join s'applique bien dans le cas d'exploration d'un arbre (répertoires) comme c'est le cas ici. diff --git a/TP5/tri fusion/GCVT.java b/TP5/tri fusion/GCVT.java new file mode 100644 index 0000000..5d90102 --- /dev/null +++ b/TP5/tri fusion/GCVT.java @@ -0,0 +1,176 @@ +//v0.1 15/11/20 (PM) (0.1 :clarification du message d'erreur principal) + +import java.io.ObjectOutputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.FileNotFoundException; +import java.nio.file.Paths; +import java.nio.file.Files; +import java.nio.file.LinkOption; +import java.io.ObjectOutputStream; +import java.io.FileOutputStream; +import java.io.FileInputStream; +import java.io.ObjectInputStream; +import java.util.Random; + +class TableauxDisque { + static boolean générer(String chemin, int taille, int max) throws IOException, FileNotFoundException { + /* génère un tableau de dimension taille, contenant des entiers aléatoires, compris entre 0 et max, + et enregistre ce tableau dans le fichier dont le chemin est fourni en argument */ + Random rdm = new Random(); + int [] tableau; + if ((taille < 1)||(max < 1)) { + System.err.println("Générer : paramètres inattendus (taille ou max < 1)"); + return false; + } else { + tableau = new int[taille]; + for (int i=0; i"+tableau[i]); + } + TableauxDisque.sauver(chemin,tableau); + System.out.println("Sauvé : "); + TableauxDisque.visualiser(chemin, 0, tableau.length); + break; + default: + throw new IllegalArgumentException( + "\nUsage : GCVT -g (pour générer un tableau" + +" de entiers inférieurs à )\n" + + " ou GCVT -c (pour comparer le contenu" + + " de 2 fichiers à partir de la position )\n" + + " ou GCVT -v (pour visualiser le contenu d'un" + + " fichier entre les positions et )\n" + + "Note : est un chemin, qui peut être relatif. Exemple ../data/t1000\n"); + } + } +} \ No newline at end of file diff --git a/TP5/tri fusion/TF.java b/TP5/tri fusion/TF.java new file mode 100644 index 0000000..90343e0 --- /dev/null +++ b/TP5/tri fusion/TF.java @@ -0,0 +1,242 @@ +//v0.2.2 15/11/21 (PM) (0.2..2 : modification (mineure) des parties à compléter) + +import java.nio.file.Paths; +import java.nio.file.Files; +import java.nio.file.LinkOption; +import java.io.IOException; +import java.io.FileNotFoundException; +import java.util.concurrent.ExecutionException; + +import java.util.List; +import java.util.LinkedList; +import java.util.ArrayList; +import java.util.Arrays; + +import java.util.concurrent.Future; +import java.util.concurrent.Callable; +import java.util.concurrent.Executors; +import java.util.concurrent.ExecutorService; + +import java.util.concurrent.RecursiveTask; +import java.util.concurrent.ForkJoinPool; + +class TriLocal implements Callable { + // pool fixe + private int début; + private int fin; + private int[] tableau; + private int[] résultat; + + TriLocal(int[] t, int d, int f) { + début = d; + fin = f; + tableau = t; + } + + @Override + public int[] call() { + return TF.traiterTronçon(tableau, début, fin); + } +} + +class TraiterFragment extends RecursiveTask { + // pool fork-join + private int début; + private int fin; + private int[] tableau; + int[] resTri = null; + + private int max = 0; + + TraiterFragment(int[] t, int d, int f) { + début = d; + fin = f; + tableau = t; + } + + @Override + protected int[] compute() { + int resteAFaire = this.fin - this.début; + // System.out.print(resteAFaire + " "); + if (resteAFaire > 1) { + TraiterFragment sp1 = new TraiterFragment(tableau, début, début + resteAFaire / 2); + TraiterFragment sp2 = new TraiterFragment(tableau, début + resteAFaire / 2, fin); + + sp1.fork(); + sp2.fork(); + + return TF.fusion(sp1.join(), sp2.join()); + } else { + return tableau; + } + } +} + +public class TF { + static int seuil; + + static int[] fusion(int[] t1, int[] t2) { + // fusionne les tableaux triés t1 et t2 en un seul tableau trié (résultat) + int[] résultat = new int[t1.length + t2.length]; + int i1 = 0; + int i2 = 0; + int iR = 0; + + while ((iR < résultat.length) && (i1 < t1.length) && (i2 < t2.length)) { + if (t1[i1] < t2[i2]) { + résultat[iR] = t1[i1]; + i1++; + } else { + résultat[iR] = t2[i2]; + i2++; + } + iR++; + } + while (i1 < t1.length) { + résultat[iR] = t1[i1]; + i1++; + iR++; + } + while (i2 < t2.length) { + résultat[iR] = t2[i2]; + i2++; + iR++; + } + return résultat; + } + + static int[] traiterTronçon(int[] t, int début, int fin) { + int taille; + int[] resTri; + // si le fragment est trop gros, on le décompose en 2 + if ((fin - début + 1) > TF.seuil) { + taille = (fin - début + 1) / 2; + return TF.fusion(traiterTronçon(t, début, début + taille - 1), traiterTronçon(t, début + taille, fin)); + } else { // traitement direct : quicksort + resTri = Arrays.copyOfRange(t, début, fin + 1); + Arrays.sort(resTri); + return resTri; + } + } + + // --------- Mono + static int[] TFMono(int[] t) { + return traiterTronçon(t, 0, t.length - 1); + } + + // -------- Pool Fixe + static int[] TFPool(ExecutorService xs, int[] t, int nbT) throws InterruptedException, ExecutionException { + + List> résultats = new ArrayList>(nbT); + int grain = Math.max(1, t.length / nbT); /* + * taille d'une recherche locale = taille du tableau/nombre de tâches + * soumises (ou 1 dans le cas (aberrant) où il y a plus de tâches que + * d'éléments du tableau) + */ + + int[] résultat = new int[t.length]; + + TriLocal[] workers = new TriLocal[nbT]; + for (int i = 0; i < workers.length; i++) { + workers[i] = new TriLocal(t, i * grain, (i + 1) * grain); + } + + // soumettre les tâches + for (int i = 0; i < workers.length; i++) { + résultats.add(xs.submit(workers[i])); + } + + // récupérer les résultats + for (int i = 0; i < workers.length; i++) { + résultat = fusion(résultat, résultats.get(i).get()); + } + return résultat; + } + + // -------- Pool ForkJoin + static int[] TFForkJoin(ForkJoinPool f, int[] t) { + TraiterFragment tout = new TraiterFragment(t, 0, t.length - 1); + return tout.compute(); + } + + // -------- main + public static void main(String[] args) + throws InterruptedException, ExecutionException, IOException, FileNotFoundException { + + int nbOuvriersPool = 0; // nb ouvriers du pool fixe. Bonne valeur : nb de processeurs disponibles + int nbEssais = 0; + int nbTâches = 0; + int tailleSeuil = 0; // nombre d'éléments du tableau en dessous duquel on effectue un tri directement + String chemin = ""; + int[] tableau; + long départ, fin; + int[] resTri; + + long[] tempsMono, tempsPool, tempsForkJoin; + + if (args.length == 5) { // analyse des paramètres + chemin = args[0]; + try { + nbEssais = Integer.parseInt(args[1]); + nbTâches = Integer.parseInt(args[2]); + tailleSeuil = Integer.parseInt(args[3]); + nbOuvriersPool = Integer.parseInt(args[4]); + } catch (NumberFormatException nfx) { + throw new IllegalArgumentException("\nUsage : TF " + " " + + " \n" + " * = nb de fragments à traiter \n" + + " * = taille pour tri direct \n"); + } + } + + if ((nbEssais < 1) || (nbTâches < 1) || (tailleSeuil < 1) || (nbOuvriersPool < 1) + || !Files.isRegularFile(Paths.get(chemin), LinkOption.NOFOLLOW_LINKS)) + throw new IllegalArgumentException("\nUsage : TF " + " " + + " \n" + " * = nb de fragments à traiter \n" + + " * = taille pour tri direct \n"); + // l'appel est correct + tempsMono = new long[nbEssais]; + tempsPool = new long[nbEssais]; + tempsForkJoin = new long[nbEssais]; + tableau = TableauxDisque.charger(chemin); + TF.seuil = tailleSeuil; + + System.out.println(Runtime.getRuntime().availableProcessors() + " processeurs disponibles pour la JVM"); + + // créer un pool avec un nombre fixe d'ouvriers + ExecutorService poule = Executors.newFixedThreadPool(nbOuvriersPool); + + // créer un pool ForkJoin + ForkJoinPool fjp = new ForkJoinPool(); + + for (int i = 0; i < nbEssais; i++) { + départ = System.nanoTime(); + resTri = TFMono(tableau); + fin = System.nanoTime(); + tempsMono[i] = (fin - départ); + TableauxDisque.sauver(chemin + "triéMono", resTri); + System.out.println("Essai [" + i + "] : durée (mono) " + tempsMono[i] / 1_000 + "µs"); + } + System.out.println("--------------------"); + + for (int i = 0; i < nbEssais; i++) { + départ = System.nanoTime(); + resTri = TFPool(poule, tableau, nbTâches); + fin = System.nanoTime(); + tempsPool[i] = (fin - départ); + TableauxDisque.sauver(chemin + "triéPF", resTri); + System.out.println("Essai [" + i + "] : durée (PF) " + tempsPool[i] / 1_000 + "µs"); + } + poule.shutdown(); + System.out.println("--------------------"); + + for (int i = 0; i < nbEssais; i++) { + départ = System.nanoTime(); + resTri = TFForkJoin(fjp, tableau); + fin = System.nanoTime(); + tempsForkJoin[i] = (fin - départ); + TableauxDisque.sauver(chemin + "triéFJ", resTri); + System.out.println("Essai [" + i + "] : durée (FJ) " + tempsForkJoin[i] / 1_000 + "µs"); + } + System.out.println("--------------------"); + } +} \ No newline at end of file diff --git a/TP5/tri fusion/array b/TP5/tri fusion/array new file mode 100644 index 0000000..1efc11f Binary files /dev/null and b/TP5/tri fusion/array differ diff --git a/TP5/tri fusion/arraytriéFJ b/TP5/tri fusion/arraytriéFJ new file mode 100644 index 0000000..ad83e7a Binary files /dev/null and b/TP5/tri fusion/arraytriéFJ differ diff --git a/TP5/tri fusion/arraytriéMono b/TP5/tri fusion/arraytriéMono new file mode 100644 index 0000000..562ae6a Binary files /dev/null and b/TP5/tri fusion/arraytriéMono differ diff --git a/TP5/tri fusion/arraytriéPF b/TP5/tri fusion/arraytriéPF new file mode 100644 index 0000000..2ae5c0b Binary files /dev/null and b/TP5/tri fusion/arraytriéPF differ diff --git a/TP6/Makefile b/TP6/Makefile new file mode 100644 index 0000000..b8cf052 --- /dev/null +++ b/TP6/Makefile @@ -0,0 +1,6 @@ + +all : + gprbuild build.gpr + +clean : + gprclean build.gpr diff --git a/TP6/README.html b/TP6/README.html new file mode 100644 index 0000000..e89e5ce --- /dev/null +++ b/TP6/README.html @@ -0,0 +1,76 @@ + + + + + + + + + + +

TP Synchronisation en Ada : lecteurs/rédacteurs

+

Objectif

+

Réaliser une tâche Ada gérant les accès à un fichier partagé, en garantissant une cohérence des accès concurrents (accès exclusif pour les écritures) et en suivant différentes stratégies de service :

+
    +
  • priorité aux rédacteurs ou aux lecteurs ;
  • +
  • FIFO ;
  • +
  • équitable (absence de famine que ce soient des lecteurs ou des rédacteurs).
  • +
+

Les fournitures et paquetages

+

mkstrategie : script shell permettant de définir, choisir et compiler une stratégie

+

LR : vide, requis par l'implantation d'Ada utilisée ici (gnat)
+LR.Affic : tout ce qui concerne l'affichage
+LR.Main : programme principal
+LR.Simu : simulateur temporel
+LR.Synchro : paquetage de redirection vers la stratégie choisie
+LR.Synchro.Exclusion : implantation LR par exclusion mutuelle (approche par conditions d'acceptation)
+LR.Synchro.Exclusion2 : implantation alternative de LR par exclusion mutuelle (approche par automates)
+LR.Synchro.Basique : 1er exercice, à compléter
+LR.Tasks : les tâches clientes (lecteurs et rédacteurs)

+

Les paquetages à consulter sont principalement les paquetages LR.Synchro.*

+

Au besoin, il est possible de consulter LR.Tasks qui contient la défintion du comportement des lecteurs et des rédacteurs. Le code est simple, et il peut être utile de jouer (en les éditant) sur les paramètres qui fixent la fréquence avec laquelle les différentes entrées sont appelées.

+

Par curiosité, on peut aussi consulter LR.Main (qui lance les tâches) et les interfaces de LR.Simu et LR.Affic mais il n'est pas utile de se pencher sur les détails de l'implantation de ces deux derniers paquetages, qui ne présente pas d'intérêt particulier.

+

Principe de la synchronisation

+

Comme présenté en cours, deux approches sont possibles :

+

1 - définir une tâche de synchronisation (par exemple LectRedTask dans LR.Synchro.Exclusion) qui possède des entrées ouvertes ou pas selon son état interne. La tâche de synchronisation est alors conçue comme un automate. L'interface (= les entrées) et leur implantation peuvent varier selon la stratégie implantée. Les procédures Demander_* et Terminer_* permettent de présenter une interface uniforme pour les tâches définies dans LR.Tasks.

+

2 - définir une tâche fournissant un service, en attente cyclique d'appels (requêtes) sur ses différentes entrées. A chaque itération, la tâche traite un appel. Elle reste en attente s'il n'y a pas de client. La démarche de conception est alors très proche de celle des moniteurs : à chaque entrée est associé un traitement, gardé par une condition d'acceptation ; la condition d'acceptation détermine la décision d'entamer (ou non) le traitement, en fonction du maintien de la cohérence (ou non) de la ressource gérée par le service.

+

À Faire

+
    +
  1. Écrire dans lr-synchro-basique.adb une version basique autorisant plusieurs lecteurs simultanés. Ne pas se préoccuper d'implanter une stratégie particulière. Pour cela, utiliser un automate fini à états (approche 1) déterminant les entrées ouvertes dans chaque état.

  2. +
  3. Définir une tâche serveur réalisant le même service (approche 2).

  4. +
  5. Modifier les versions précédentes pour implanter une stratégie de type priorité aux rédacteurs.

  6. +
  7. Modifier les versions précédentes pour implanter une stratégie FIFO.

  8. +
  9. Construire un automate pour une stratégie équitable (autre que FIFO), i.e. qui garantit l'absence de famine des lecteurs et des rédacteurs, et implanter cet automate.

  10. +
+

Définition des stratégies

+

L'absence d'introspection en Ada, qui aurait par exemple permis de paramétrer LR.Synchro avec le nom des paquetages/stratégies disponibles, alourdit un peu le travail d'édition des différents composants.

+

Le script shell ./mkstrategie vise à faciliter ce travail d'édition :

+
    +
  • appelé sans paramètres (./mkstrategie), il permet de recompiler la dernière stratégie "installée" ;
  • +
  • avec l'option -i (./mkstrategie -i), il permet de créer et installer une nouvelle stratégie, d'installer une stratégie existante, ou de recompiler la dernière stratégie installée.
  • +
+

Pour définir une stratégie (Xxxx), il suffit de travailler sur le fichier lr-synchro-xxxx.adb, correspondant à son implantation. Seul le corps de la tâche LectRedTask, ainsi que la fonction Nom_Strategie doivent être modifiés. En particulier, les procédures Demander_Lecture, Demander_Ecriture, Terminer_Lecture, Terminer_Ecriture qui appellent les entrées correspondantes de la tâche LectRedTask n'ont pas à être modifiées (sauf, bien sûr, si l'on souhaite modifier l'interface de la tâche LectRedTask). Leur rôle est d'abord d'assurer une modularité, en permettant leur appel (depuis d'autres paquetages) en tant que procédures de paquetages, sans référence à la tâche LectRedTask.

+

Il est par ailleurs possible de réaliser les opérations du script ./mkstrategie à la main. Le détail des opérations est donné en fin de document.

+

Pour exécuter

+
./lr-main 5 4    (nb lecteurs, nb rédacteurs)
+

Note : le bouton d'aide de la fenêtre affichée par l'application en présente les fonctionnalités.

+

Annexe

+

Rappel tâches Ada

+
    +
  • Pour une entrée de rendez-vous Rdv, Rdv'count est le nombre de tâches clientes en attente de l'acceptation du rendez-vous.
  • +
+

Ajouter une nouvelle stratégie sans utiliser le script mkstrategie

+

Soit la stratégie Toto que l'on souhaite implanter :

+
    +
  • dupliquer lr-synchro.ads dans lr-synchro-toto.ads ;
  • +
  • éditer lr-synchro-toto.ads pour nommer le paquetage LR.Synchro.Toto ; toutes les stratégies ont ainsi la même interface, mais des noms différents.
  • +
  • dans lr-synchro.adb, remplacer LR.Synchro.Exclusion (en supposant que Exclusion est la dernière stratégie utilisée) par LR.Synchro.Toto (deux emplacements marqués par XXXX) ; cette manipulation (pas vraiment élégante...) vise à pallier l'absence d'introspection en Ada, qui aurait ici permis de paramétrer LR.Synchro avec le nom des paquetages/stratégies disponibles.
  • +
  • écrire l'implantation du paquetage LR.Synchro.Toto dans le fichier lr-synchro-toto.adb en s'inspirant d'une stratégie déjà existante.
  • +
+

Pour compiler

+
    +
  • assurez vous d'utiliser le compilateur Ada situé dans usr/bin (au besoin : export PATH=/usr/bin:$PATH)
  • +
  • make ou gprbuild build.gpr ou ./mkstrategie
  • +
+ + diff --git a/TP6/README.md b/TP6/README.md new file mode 100644 index 0000000..86e72fa --- /dev/null +++ b/TP6/README.md @@ -0,0 +1,137 @@ +TP Synchronisation en Ada : lecteurs/rédacteurs +=============================================== + +Objectif +-------- +Réaliser une tâche Ada gérant les accès à un fichier partagé, en garantissant une cohérence des accès concurrents (accès exclusif pour les écritures) et en suivant différentes stratégies de service : + +- priorité aux rédacteurs ou aux lecteurs ; +- FIFO ; +- équitable (absence de famine que ce soient des lecteurs ou des rédacteurs). + +Les fournitures et paquetages +----------------------------- + +`mkstrategie` : script shell permettant de définir, choisir et compiler une stratégie + +`LR` : vide, requis par l'implantation d'Ada utilisée ici (gnat) +`LR.Affic` : tout ce qui concerne l'affichage +`LR.Main` : programme principal +`LR.Simu` : simulateur temporel +`LR.Synchro` : paquetage de redirection vers la stratégie choisie +`LR.Synchro.Exclusion` : implantation LR par exclusion mutuelle (approche par conditions d'acceptation) +`LR.Synchro.Exclusion2` : implantation alternative de LR par exclusion mutuelle (approche par automates) +`LR.Synchro.Basique` : 1er exercice, à compléter +`LR.Tasks` : les tâches clientes (lecteurs et rédacteurs) + +Les paquetages à consulter sont principalement les paquetages `LR.Synchro.*` + +Au besoin, il est possible de consulter `LR.Tasks` qui contient la défintion du comportement +des lecteurs et des rédacteurs. Le code est simple, et il peut être utile de jouer (en les +éditant) sur les paramètres qui fixent la fréquence avec laquelle les différentes entrées +sont appelées. + +Par curiosité, on peut aussi consulter `LR.Main` (qui lance les tâches) et les interfaces +de `LR.Simu` et `LR.Affic` mais il n'est pas utile de se pencher sur les détails de +l'implantation de ces deux derniers paquetages, qui ne présente pas d'intérêt particulier. + +Principe de la synchronisation +------------------------------ + +Comme présenté en cours, deux approches sont possibles : + +1 - définir une tâche de synchronisation (par exemple `LectRedTask` dans `LR.Synchro.Exclusion`) qui +possède des entrées ouvertes ou pas selon son état interne. La tâche de synchronisation +est alors conçue comme un *automate*. L'interface (= +les entrées) et leur implantation peuvent varier selon la stratégie implantée. +Les procédures `Demander_*` et `Terminer_*` permettent de présenter une interface uniforme pour +les tâches définies dans `LR.Tasks`. + +2 - définir une tâche fournissant un *service*, en attente cyclique d'appels (requêtes) sur +ses différentes entrées. A chaque itération, la tâche traite un appel. Elle reste en attente +s'il n'y a pas de client. La démarche de conception est alors très proche de celle des + moniteurs : à chaque entrée est associé un traitement, gardé par une condition + d'acceptation ; la condition d'acceptation détermine la décision d'entamer (ou non) + le traitement, en fonction du maintien de la cohérence (ou non) de la ressource gérée + par le service. + + +À Faire +------- + +1. Écrire dans `lr-synchro-basique.adb` une version basique autorisant +plusieurs lecteurs simultanés. Ne pas se préoccuper d'implanter une +stratégie particulière. Pour cela, utiliser un automate fini à états (approche 1) +déterminant les entrées ouvertes dans chaque état. + +2. Définir une tâche serveur réalisant le même service (approche 2). + +3. Modifier les versions précédentes pour implanter une stratégie de +type priorité aux rédacteurs. + +4. Modifier les versions précédentes pour implanter une stratégie FIFO. + +5. Construire un automate pour une stratégie équitable (autre que FIFO), i.e. qui garantit +l'absence de famine des lecteurs et des rédacteurs, et implanter cet automate. + +Définition des stratégies +------------------------- + +L'absence d'introspection en Ada, qui aurait par exemple permis de paramétrer `LR.Synchro` + avec le nom des paquetages/stratégies disponibles, alourdit un peu le travail d'édition + des différents composants. + + Le script shell `./mkstrategie` vise à faciliter ce travail d'édition : + + - appelé sans paramètres (`./mkstrategie`), il permet de recompiler la dernière stratégie "installée" ; + - avec l'option -i (`./mkstrategie -i`), il permet de créer et installer une nouvelle stratégie, +d'installer une stratégie existante, ou de recompiler la dernière stratégie installée. + +Pour définir une stratégie (`Xxxx`), il suffit de travailler sur le fichier +`lr-synchro-xxxx.adb`, correspondant à son implantation. Seul le corps de la tâche + `LectRedTask`, ainsi que la fonction `Nom_Strategie` doivent être + modifiés. En particulier, les procédures `Demander_Lecture`, `Demander_Ecriture`, + `Terminer_Lecture`, `Terminer_Ecriture` qui appellent les entrées correspondantes de + la tâche `LectRedTask` n'ont pas à être modifiées (sauf, bien sûr, si l'on souhaite modifier l'interface de la tâche `LectRedTask`). Leur rôle est d'abord d'assurer + une modularité, en permettant leur appel (depuis d'autres paquetages) en tant que procédures de paquetages, sans référence à la tâche `LectRedTask`. + + Il est par ailleurs possible de réaliser les opérations du script `./mkstrategie` + à la main. Le détail des opérations est donné en fin de document. + +Pour exécuter +------------- + + ./lr-main 5 4 (nb lecteurs, nb rédacteurs) + +*Note :* le bouton d'aide de la fenêtre affichée par l'application en présente les fonctionnalités. + + +Annexe +======= + +Rappel tâches Ada +----------------- + +- Pour une entrée de rendez-vous `Rdv`, `Rdv'count` est le nombre de tâches + clientes en attente de l'acceptation du rendez-vous. + +Ajouter une nouvelle stratégie sans utiliser le script `mkstrategie` +----------------------------------- + +Soit la stratégie `Toto` que l'on souhaite implanter : + +- dupliquer `lr-synchro.ads` dans `lr-synchro-toto.ads` ; +- éditer `lr-synchro-toto.ads` pour nommer le paquetage `LR.Synchro.Toto` ; +toutes les stratégies ont ainsi la même interface, mais des noms différents. +- dans `lr-synchro.adb`, remplacer `LR.Synchro.Exclusion` (en supposant que `Exclusion` est +la dernière stratégie utilisée) par `LR.Synchro.Toto` (deux emplacements marqués par `XXXX`) ; +cette manipulation (pas vraiment élégante...) vise à pallier l'absence d'introspection en Ada, +qui aurait ici permis de paramétrer `LR.Synchro` avec le nom des paquetages/stratégies disponibles. +- écrire l'implantation du paquetage `LR.Synchro.Toto` + dans le fichier `lr-synchro-toto.adb` en s'inspirant d'une stratégie déjà existante. + + +Pour compiler +------------- + - assurez vous d'utiliser le compilateur Ada situé dans `usr/bin` (au besoin : `export PATH=/usr/bin:$PATH`) + - `make` ou `gprbuild build.gpr` ou `./mkstrategie` diff --git a/TP6/src/build.gpr b/TP6/src/build.gpr new file mode 100644 index 0000000..9994814 --- /dev/null +++ b/TP6/src/build.gpr @@ -0,0 +1,4 @@ +With "gtkada"; +project Build is + for Main use ("lr-main"); +end Build; diff --git a/TP6/src/lectred.glade b/TP6/src/lectred.glade new file mode 100644 index 0000000..0e754ad --- /dev/null +++ b/TP6/src/lectred.glade @@ -0,0 +1,358 @@ + + + + + + 1 + 100 + 1 + 1 + 10 + + + False + Lecteurs/Rédacteurs + 350 + 400 + + + + + + + True + False + vertical + + + True + False + 40 + True + + + Quitter + True + True + True + + + + False + True + 0 + + + + + Pause + True + True + True + + + + False + True + 1 + + + + + Aide + True + True + True + + + + False + True + 2 + + + + + False + True + 0 + + + + + True + False + + + False + True + 5 + 1 + + + + + True + False + Stratégie + center + + + False + True + 2 + + + + + True + False + + + False + True + 5 + 3 + + + + + True + False + REDACTEURS + center + + + False + True + 4 + + + + + True + False + vertical + + + True + False + GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_STRUCTURE_MASK + + + + + + True + True + 0 + + + + + True + False + 0.5 + + + True + False + 0 + 0 + + + True + False + GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_STRUCTURE_MASK + + + + + + + + + + + True + True + 1 + + + + + True + False + GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_STRUCTURE_MASK + + + + True + True + 2 + + + + + True + True + 5 + + + + + True + False + LECTEURS + center + + + False + True + 6 + + + + + True + False + + + False + True + 5 + 7 + + + + + True + False + + + True + False + + + True + False + Vitesse du temps: + + + False + True + 0 + + + + + True + True + adjustment1 + 100 + 1 + 0 + bottom + + + True + True + 1 + + + + + + + False + True + 9 + + + + + + + False + Aide + 480 + 300 + dialog + + + + + + False + vertical + 2 + + + False + end + + + Fermer + True + True + True + + + + True + True + 0 + + + + + False + False + 0 + + + + + True + True + in + + + True + False + + + True + False + Lecteurs/Rédacteurs +------------------- + +Problème: deux classes de processus sont en compétition pour accéder à un fichier; les lecteurs peuvent être concurrents mais qu'entre eux, et les rédacteurs sont exclusifs vis-à-vis de tous. + +Interprétation du dessin: + - les disques noirs sont les rédacteurs, et les cercles noirs sont les lecteurs; + - un processus dans le rectangle central possède l'accès; + - un processus à proximité du rectangle demande l'accès. + +Actions: + - en cliquant dans le rectangle, vous forcez une fin d'utilisation; + - en cliquant côté lecteur, vous forcez une demande de lecture; + - en cliquant côté rédacteur, vous forcez une demande d'écriture; + - pause permet de suspendre le temps de la simulation. Les actions forcées sont par contre toujours possibles; + - vous pouvez régler la vitesse de la simulation avec l'échelle du bas. + True + + + + + + + True + True + 1 + + + + + + diff --git a/TP6/src/lr-affic.adb b/TP6/src/lr-affic.adb new file mode 100644 index 0000000..90e81df --- /dev/null +++ b/TP6/src/lr-affic.adb @@ -0,0 +1,352 @@ +-- Time-stamp: <09 nov 2019 09:54 queinnec@enseeiht.fr> + +with Ada.Text_IO; use Ada.Text_IO; +with Ada.Tags; +with Ada.Numerics.Discrete_Random; +with Glib; use Glib; +with Gtkada.Builder; +with Gtkada.Handlers; +with Gdk.Threads; +with Glib.Error; +with Gtk.Widget; use Gtk.Widget; +with Gtk.Label; +with Gtk.GRange; +with Gtk.Button; +with Cairo; +with LR.Tasks; use LR.Tasks; -- just for Proc_Id +with LR.Simu; + +package body LR.Affic is + + Nb_Lecteurs : Natural; + Nb_Redacteurs : Natural; + + Dialog_Aide : Gtk.Widget.Gtk_Widget; + Bouton_Pause : Gtk.Button.Gtk_Button; + Drawing_Areas : Gtk.Widget.Gtk_Widget; + + type Point is record + X : Natural; + Y : Natural; + end record; + + type Objet is record + Etat : Proc_Etat; + Pos_Rien : Point; + Pos_Demande : Point; + Pos_Utilise : Point; + end record; + + type Les_Objets_Array is array(Positive range <>) of Objet; + Les_Redacteurs : access Les_Objets_Array; + Les_Lecteurs : access Les_Objets_Array; + + -- Event "clicked" on button "aide" + procedure Aide_Afficher(Builder: access Gtkada.Builder.Gtkada_Builder_Record'class) is + begin + Dialog_Aide.Show; + end Aide_Afficher; + + -- Event "clicked" on button "fermer" + procedure Aide_Fermer(Builder: access Gtkada.Builder.Gtkada_Builder_Record'class) is + begin + Dialog_Aide.Hide; + end Aide_Fermer; + + -- Event clicked on button "pause". + procedure Pause_Or_Run(Builder: access Gtkada.Builder.Gtkada_Builder_Record'class) is + begin + LR.Simu.Swap_Running; + if LR.Simu.Get_Running then + Bouton_Pause.Set_Label("Pause"); + else + Bouton_Pause.Set_Label("Run"); + end if; + end Pause_Or_Run; + + -- Event "clicked" on button "paramètres" + -- procedure Show_Param(Builder: access Gtkada.Builder.Gtkada_Builder_Record'class) is + -- begin + -- -- Gtk.Widget.Show(Dialog_Parametres); + -- null; + -- end Show_Param; + + -- Event "value_changed" de l'ajustement de l'échelle de vitesse du temps. + procedure Set_Timespeed(Scale : access Gtk_Widget_Record'Class) is + Val : Natural; + Self : Gtk.GRange.Gtk_Range := Gtk.GRange.Gtk_Range(Scale); + begin + Val := Natural(Self.Get_Adjustment.Get_Value); + LR.Simu.Set_Timespeed(Val); + end Set_Timespeed; + + procedure Quitter(Builder: access Gtkada.Builder.Gtkada_Builder_Record'class) is + procedure Libc_Exit(Status: Natural); + pragma Import(C, Libc_Exit, "exit"); + begin + Libc_Exit(0); + end Quitter; + + ---------------------------------------------------------------- + + package Random_Proc is + function Get(Min, Max: Proc_Id) return Proc_Id; + end Random_Proc; + package body Random_Proc is + package Rand_Proc is new Ada.Numerics.Discrete_Random(Proc_Id); + Generator : Rand_Proc.Generator; + function Get(Min, Max: Proc_Id) return Proc_Id Is + X : Proc_Id; + begin + loop + X := Rand_Proc.Random(Generator); + exit when (X >= Min and X <= Max); + end loop; + return X; + end Get; + begin + Rand_Proc.Reset(Generator); + end Random_Proc; + + -- Cherche dans le tableau un processus dans l'état. + -- S'il y en a au moins un, un processus *au hasard* (dans cet état) est réveillé. + function Chercher_Dormeur (Tab: access Les_Objets_Array; Etat: Proc_Etat) return Boolean is + Base : Proc_Id; + begin + -- Tirons au hasard le point de départ, puis on parcourt circulairement. + Base := Random_Proc.Get(Tab'First, Tab'Last); + for i in Base .. Tab'Last loop + if Tab(i).Etat = Etat then + LR.Simu.wakeup (i); + return True; + end if; + end loop; + for i in Tab'First..Base-1 loop + if Tab(i).Etat = Etat then + LR.Simu.wakeup (i); + return True; + end if; + end loop; + return False; + end Chercher_Dormeur; + + function Click_Event_Red(Builder: access Gtkada.Builder.Gtkada_Builder_Record'class) return Boolean is + begin + return Chercher_Dormeur(Les_Redacteurs, Redacteur_Rien); + end Click_Event_Red; + + function Click_Event_Lect(Builder: access Gtkada.Builder.Gtkada_Builder_Record'class) return Boolean is + begin + return Chercher_Dormeur(Les_Lecteurs, Lecteur_Rien); + end Click_Event_Lect; + + function Click_Event_Inside(Builder: access Gtkada.Builder.Gtkada_Builder_Record'class) return Boolean is + begin + return + Chercher_Dormeur(Les_Lecteurs, Lecteur_Lit) + or else Chercher_Dormeur(Les_Redacteurs, Redacteur_Ecrit); + end Click_Event_Inside; + + ---------------------------------------------------------------- + + -- Event configure (= realize or resize) on window redacteur. + -- XXXX Hypothèses: + -- - les trois fenêtres rédacteur, inside et lecteur ont la même hauteur; + -- - la fenêtre inside est deux fois moins large que la fenêtre rédacteur; + -- - la fenêtre rédacteur est en haut, la fenêtre lecteur en bas. + function Compute_Placement(Builder: access Gtkada.Builder.Gtkada_Builder_Record'class) return Boolean is + + Taille_Fen_Dem_X, Taille_Fen_Inside_X : Float; + Taille_Fen_Dem_Y, Taille_Fen_Inside_Y : Float; + Interv_Rien, Interv_Demande, Interv_Utilise : Float; + Start_Rien, Start_Demande, Start_Utilise : Float; + Allocation : Gtk.Widget.Gtk_Allocation; + begin + Put_Line("Event configure"); + Gtk.Widget.Get_Allocation(Gtk_Widget(Gtkada.Builder.Get_Object(Builder, "drawingarea_redacteur")), Allocation); + Taille_Fen_Dem_X := Float(Allocation.Width); + Taille_Fen_Dem_Y := Float(Allocation.Height); + + Taille_Fen_Inside_X := Taille_Fen_Dem_X / 2.0; + Taille_Fen_Inside_Y := Taille_Fen_Dem_Y; + + Interv_Rien := Taille_Fen_Dem_X / (Float(Nb_Lecteurs - 1) + 4.0); + Interv_Utilise := Taille_Fen_Inside_X / (Float(Nb_Lecteurs - 1) + 4.0); + Interv_Demande := Interv_Utilise; + Start_Rien := 2.0 * Interv_Rien; + Start_Utilise := 2.0 * Interv_Utilise; + Start_Demande := (taille_fen_dem_x - taille_fen_inside_x) / 2.0 + Start_Utilise; + + for i in Les_Lecteurs'range loop + Les_Lecteurs(i).Pos_Rien.x := Natural(Start_Rien); + Les_Lecteurs(i).Pos_Rien.y := Natural(Taille_Fen_Dem_Y * 0.75); + Les_Lecteurs(i).Pos_Demande.x := Natural(Start_Demande); + Les_Lecteurs(i).Pos_Demande.y := Natural(Taille_Fen_Dem_Y * 0.25); + Les_Lecteurs(i).Pos_Utilise.x := Natural(Start_Utilise); + Les_Lecteurs(i).Pos_Utilise.y := Natural(Taille_Fen_Inside_Y * 0.75); + Start_Rien := Start_Rien + Interv_Rien; + Start_Demande := Start_Demande + Interv_Demande; + Start_Utilise := Start_Utilise + Interv_Utilise; + end loop; + + -- Les rédacteurs + Interv_Rien := Taille_Fen_Dem_X / (Float(Nb_Redacteurs - 1) + 4.0); + Interv_Utilise := Taille_Fen_Inside_X / (Float(Nb_Redacteurs - 1) + 4.0); + Interv_Demande := Interv_Utilise; + Start_Rien := 2.0 * Interv_Rien; + Start_Utilise := 2.0 * Interv_Utilise; + Start_Demande := (taille_fen_dem_x - taille_fen_inside_x) / 2.0 + Start_Utilise; + + for i in Les_Redacteurs'range loop + Les_Redacteurs(i).Pos_Rien.x := Natural(Start_Rien); + Les_Redacteurs(i).Pos_Rien.y := Natural(Taille_Fen_Dem_Y * 0.25); + Les_Redacteurs(i).Pos_Demande.x := Natural(Start_Demande); + Les_Redacteurs(i).Pos_Demande.y := Natural(Taille_Fen_Dem_Y * 0.90); + Les_Redacteurs(i).Pos_Utilise.x := Natural(Start_Utilise); + Les_Redacteurs(i).Pos_Utilise.y := Natural(Taille_Fen_Inside_Y * 0.25); + Start_Rien := Start_Rien + Interv_Rien; + Start_Demande := Start_Demande + Interv_Demande; + Start_Utilise := Start_Utilise + Interv_Utilise; + end loop; + + return False; + end Compute_Placement; + + ---------------------------------------------------------------- + + procedure Tracer_Cercle(Context: Cairo.Cairo_Context; Fill : Boolean; Center: Point) is + Rayon : constant Natural := 5; + begin + Cairo.Set_Line_Width(Context, 2.0); + Cairo.Set_Source_RGB(Context, 0.0, 0.0 ,0.0); + Cairo.Arc(Context, Gdouble(Center.X - Rayon), Gdouble(Center.Y - Rayon), Gdouble(Rayon*2), 0.0, 2.0 * 3.14159); + if Fill then Cairo.Fill(Context); else Cairo.Stroke(Context); end if; + end Tracer_Cercle; + + function Draw_Callback_Red(Widget : access Gtk_Widget_Record'Class; Context: Cairo.Cairo_Context) return Boolean is + begin + for I in Les_Redacteurs'range loop + case Les_Redacteurs(I).Etat is + when Redacteur_Demande => + Tracer_Cercle(Context, True, Les_Redacteurs(i).Pos_Demande); + when Redacteur_Rien => + Tracer_Cercle(Context, True, Les_Redacteurs(i).Pos_Rien); + when others => null; + end case; + end loop; + return True; + end Draw_Callback_Red; + + function Draw_Callback_Lec(Widget : access Gtk_Widget_Record'Class; Context: Cairo.Cairo_Context) return Boolean is + begin + for I in Les_Lecteurs'range loop + case Les_Lecteurs(I).Etat is + when Lecteur_Demande => + Tracer_Cercle(Context, False, Les_Lecteurs(i).Pos_Demande); + when Lecteur_Rien => + Tracer_Cercle(Context, False, Les_Lecteurs(i).Pos_Rien); + when others => null; + end case; + end loop; + return True; + end Draw_Callback_Lec; + + function Draw_Callback_Inside(Widget : access Gtk_Widget_Record'Class; Context: Cairo.Cairo_Context) return Boolean is + begin + for I in Les_Lecteurs'range loop + case Les_Lecteurs(I).Etat is + when Lecteur_Lit => + Tracer_Cercle(Context, False, Les_Lecteurs(i).Pos_Utilise); + when others => null; + end case; + end loop; + for I in Les_Redacteurs'range loop + case Les_Redacteurs(I).Etat is + when Redacteur_Ecrit => + Tracer_Cercle(Context, True, Les_Redacteurs(i).Pos_Utilise); + when others => null; + end case; + end loop; + return True; + end Draw_Callback_Inside; + + ---------------------------------------------------------------- + + procedure Init (Nomstrategie: String; Nblect : Natural; Nbred: Natural) is + Builder : Gtkada.Builder.Gtkada_Builder; + begin + Nb_Lecteurs := NbLect; + Nb_Redacteurs := NbRed; + -- XXXX les id doivent être compatibles avec LR.Main.Start_Client_Tasks + Les_Lecteurs := new Les_Objets_Array(1 .. Nb_Lecteurs); + Les_Redacteurs := new Les_Objets_Array(Nb_Lecteurs+1 .. Nb_Lecteurs + Nb_Redacteurs); + for I in Les_Lecteurs'range loop + Les_Lecteurs(I).Etat := Lecteur_Rien; + end loop; + for I in Les_Redacteurs'range loop + Les_Redacteurs(I).Etat := Redacteur_Rien; + end loop; + + Gtkada.Builder.Gtk_New (Builder); + declare + Error : aliased Glib.Error.GError; + Useless : GUint; + begin + Useless := Builder.Add_From_File("lectred.glade", Error'Access); + end; + Dialog_Aide := Gtk_Widget(Builder.Get_Object("dialog_aide")); + Bouton_Pause := Gtk.Button.Gtk_Button(Builder.Get_Object("bouton_pause")); + Drawing_Areas := Gtk_Widget(Builder.Get_Object("drawingarea_all")); + + declare + Nom_Strategie : Gtk.Label.Gtk_Label := Gtk.Label.Gtk_Label(Builder.Get_Object("label_strategie")); + begin + Nom_Strategie.Set_Text(Nomstrategie); + end; + + declare + Timespeed_Scale : Gtk.GRange.Gtk_Range := Gtk.GRange.Gtk_Range(Builder.Get_Object("timespeed")); + begin + Gtkada.Handlers.Widget_Callback.Connect(Timespeed_Scale, "value-changed", Set_Timespeed'Access); + Set_Timespeed(Timespeed_Scale); + end; + + Builder.Register_Handler ("aff_aide_afficher", Aide_Afficher'Access); + Builder.Register_Handler ("aff_aide_fermer", Aide_Fermer'Access); + Builder.Register_Handler ("aff_click_event_red", Click_Event_Red'Access); + Builder.Register_Handler ("aff_click_event_lect", Click_Event_Lect'Access); + Builder.Register_Handler ("aff_click_event_inside", Click_Event_Inside'Access); + Builder.Register_Handler ("aff_compute_placement", Compute_Placement'Access); + -- Builder.Register_Handler ("aff_expose", Expose'Access); + Builder.Register_Handler ("aff_pause_or_run", Pause_Or_Run'Access); + -- Builder.Register_Handler ("aff_show_param", Show_Param'Access); + Builder.Register_Handler ("aff_quitter", Quitter'Access); + Builder.Do_Connect; + + declare + Xfen_Redacteur : Gtk_Widget := Gtk_Widget(Builder.Get_Object("drawingarea_redacteur")); + Xfen_Lecteur : Gtk_Widget := Gtk_Widget(Builder.Get_Object("drawingarea_lecteur")); + Xfen_Inside : Gtk_Widget := Gtk_Widget(Builder.Get_Object("drawingarea_inside")); + begin + Xfen_Redacteur.On_Draw(Draw_Callback_Red'Access); + Xfen_Lecteur.On_Draw(Draw_Callback_Lec'Access); + Xfen_Inside.On_Draw(Draw_Callback_Inside'Access); + end; + + Gtk.Widget.Show_All(Gtk_Widget(Builder.Get_Object("LectRed"))); + end Init; + + procedure Changer_Etat(Id: Proc_Id; Etat: Proc_Etat) is + begin + Gdk.Threads.Enter; + -- Put_Line(Natural'Image(Id) & " -> " & Proc_Etat'Image(Etat)); + if Id in Les_Lecteurs'Range then + Les_Lecteurs(Id).Etat := Etat; + else + Les_Redacteurs(Id).Etat := Etat; + end if; + Drawing_Areas.Queue_Draw; + Gdk.Threads.Leave; + end Changer_Etat; + + end LR.Affic; diff --git a/TP6/src/lr-affic.ads b/TP6/src/lr-affic.ads new file mode 100644 index 0000000..839c398 --- /dev/null +++ b/TP6/src/lr-affic.ads @@ -0,0 +1,14 @@ +-- Time-stamp: <01 oct 2012 15:40 queinnec@enseeiht.fr> + +with LR.Tasks; use LR.Tasks; + +package LR.Affic is + + type Proc_Etat is (Redacteur_Demande, Redacteur_Ecrit, Redacteur_Rien, Lecteur_Demande, Lecteur_Lit, Lecteur_Rien); + + procedure Init (Nomstrategie: String; Nblect : Natural; Nbred: Natural); + + -- Change l'état d'affichage : place du rond blanc/noir + procedure Changer_Etat(Id : Proc_Id; Etat: Proc_Etat); + +end LR.Affic; diff --git a/TP6/src/lr-main.adb b/TP6/src/lr-main.adb new file mode 100644 index 0000000..f7e80b1 --- /dev/null +++ b/TP6/src/lr-main.adb @@ -0,0 +1,61 @@ +-- Time-stamp: <23 oct 2012 10:40 queinnec@enseeiht.fr> + +with Ada.Text_IO; +with Ada.Command_Line; +with Ada.Exceptions; +use Ada; +with Gdk.Threads; +with Gtk.Main; +with LR.Affic; +with LR.Tasks; +with LR.Simu; +with LR.Synchro; + +procedure LR.Main is + Nb_Lecteurs : Natural; + Nb_Redacteurs : Natural; + + procedure Start_Client_Tasks is + Tl : LR.Tasks.Lecteur_Access; + Tr : LR.Tasks.Redacteur_Access; + begin + for I in 1..Nb_Lecteurs loop + Tl := new LR.Tasks.Lecteur; + Tl.Init(I); + end loop; + for I in 1 .. Nb_Redacteurs loop + Tr := new LR.Tasks.Redacteur; + Tr.Init(Nb_Lecteurs + I); + end loop; + end Start_Client_Tasks; + +begin + if Ada.Command_Line.Argument_Count /= 2 then + Text_IO.Put_Line("Expected two arguments : nblect nbred."); + return; + end if; + + Nb_Lecteurs := Natural'Value(Ada.Command_Line.Argument(1)); + Nb_Redacteurs := Natural'Value(Ada.Command_Line.Argument(2)); + + if (Nb_Redacteurs + Nb_Lecteurs > LR.Tasks.MAXPROCS) then + Text_IO.Put_Line("nblect >= 1, nbred >= 1, nblect+nbred <=" & Natural'Image(LR.Tasks.MAXPROCS)); + return; + end if; + + Gdk.Threads.G_Init; + Gdk.Threads.Init; + Gtk.Main.Init; + + LR.Simu.Init(Nb_Redacteurs + Nb_Lecteurs); + LR.Affic.Init(Nomstrategie => LR.Synchro.Nom_Strategie, Nblect => Nb_Lecteurs, Nbred => Nb_Redacteurs); + Start_Client_Tasks; + + Gdk.Threads.Enter; + Gtk.Main.Main; + Gdk.Threads.Leave; + +exception + when Error: others => + Text_IO.Put_Line("**** LectRedTask: exception: " & Ada.Exceptions.Exception_Information(Error)); +end LR.Main; diff --git a/TP6/src/lr-simu.adb b/TP6/src/lr-simu.adb new file mode 100644 index 0000000..6ef646e --- /dev/null +++ b/TP6/src/lr-simu.adb @@ -0,0 +1,83 @@ +-- Time-stamp: <08 nov 2019 13:21 queinnec@enseeiht.fr> + +with Ada.Text_IO; use Ada.Text_IO; +with LR.Tasks; use LR.Tasks; + +package body LR.Simu is + + Is_Running : Boolean := True; + Nb_Procs : Positive; + Timespeed : Positive := 1; -- in 1..100 + Basefreq : constant Positive := 100; -- sleep unit : 1/Basefreq + + type Proc_Sleep_Record is + record + Duree: Natural := 0; + Wakeup: Boolean := False; + end record; + + Infos_Proc : array(Proc_Id) of Proc_Sleep_Record; + + procedure Init(Nbproc: Positive) is + begin + Nb_Procs := Nbproc; + Timespeed := 1; + end Init; + + procedure Sleep(Id: Proc_Id; Duree: Positive) is + begin + Infos_Proc(Id).Duree := Duree * Basefreq; + Infos_Proc(Id).Wakeup := False; + while (not Infos_Proc(Id).Wakeup) and (Infos_Proc(Id).Duree > 0) loop + if (Is_Running) then + Infos_Proc(Id).Duree := Infos_Proc(Id).Duree - 1; + end if; + delay (1.0 / Basefreq) / Timespeed; + end loop; + end Sleep; + + procedure Wakeup(Id: Proc_Id) is + begin + Put_Line (Proc_Id'Image(Id) & " Wakeup by user"); + Infos_Proc(Id).Wakeup := True; + end Wakeup; + + function Get_Running return Boolean is + begin + return Is_Running; + end Get_Running; + + procedure Set_Running(B: Boolean) is + begin + Is_Running := B; + end Set_Running; + + procedure Swap_Running is + begin + Is_Running := not Is_Running; + end Swap_Running; + + procedure Set_Timespeed(Ts: Integer) is + begin + Timespeed := TS; + end Set_Timespeed; + + function Get_Timespeed return Integer is + begin + return Timespeed; + end Get_Timespeed; + + Previous_Running : Boolean; + + procedure Suspend_Time is + begin + Previous_Running := Is_Running; + Is_Running := False; + end Suspend_Time; + + procedure Resume_Time is + begin + Is_Running := Previous_Running; + end Resume_Time; + +end LR.Simu; diff --git a/TP6/src/lr-simu.ads b/TP6/src/lr-simu.ads new file mode 100644 index 0000000..da46b49 --- /dev/null +++ b/TP6/src/lr-simu.ads @@ -0,0 +1,40 @@ +-- Time-stamp: <02 oct 2012 09:38 queinnec@enseeiht.fr> + +with LR.Tasks; + +-- Simulateur temporel, avec possibilité de suspendre l'écoulement du temps, +-- de varier la vitesse du temps, et d'interrompre un sommeil. +package LR.Simu is + + -- Initialise le simulateur de temps pour `Nbproc' processus. + procedure Init(Nbproc: Positive); + + -- Suspend l'exécution du processus appelant, qui s'identifie par `Id', pour la durée spécifiée. + procedure Sleep(Id: LR.Tasks.Proc_Id; Duree: Positive); + + -- Interrompt le sommeil du processus `Id'. Sans effet si le processus ne dort pas. + procedure Wakeup(Id: LR.Tasks.Proc_Id); + + -- Renvoie la situation courante d'écoulement du temps. + function Get_Running return Boolean; + + -- Décide de l'écoulement du temps. + procedure Set_Running(B: Boolean); + + -- Inverse la situation courante d'écoulement du temps. + procedure Swap_Running; + + -- Positionne la vitesse d'écoulement du temps. + procedure Set_Timespeed(Ts: Integer); + + -- Obtention de la vitesse d'écoulement du temps. + function Get_Timespeed return Integer; + + -- Suspend l'écoulement du temps en sauvegardant la situation courante (running ou pas). + -- Doit éventuellement être suivi par un appel à Resume_Time. + procedure Suspend_Time; + + -- Restaure la situation de l'écoulement du temps avant le précédent `Suspend_Time' + procedure Resume_Time; + +end LR.Simu; diff --git a/TP6/src/lr-synchro-basique.adb b/TP6/src/lr-synchro-basique.adb new file mode 100644 index 0000000..aed2212 --- /dev/null +++ b/TP6/src/lr-synchro-basique.adb @@ -0,0 +1,50 @@ +with Ada.Text_IO; use Ada.Text_IO; +with Ada.Exceptions; + +-- Lecteurs concurrents, approche automate. Pas de politique d'accès. +package body LR.Synchro.Basique is + + function Nom_Strategie return String is + begin + return "Automate, lecteurs concurrents, sans politique d'accès"; + end Nom_Strategie; + + task LectRedTask is + entry Demander_Lecture; + entry Demander_Ecriture; + entry Terminer_Lecture; + entry Terminer_Ecriture; + end LectRedTask; + + task body LectRedTask is + begin + loop + -- TODO + null; + end loop; + exception + when Error: others => + Put_Line("**** LectRedTask: exception: " & Ada.Exceptions.Exception_Information(Error)); + end LectRedTask; + + procedure Demander_Lecture is + begin + LectRedTask.Demander_Lecture; + end Demander_Lecture; + + procedure Demander_Ecriture is + begin + LectRedTask.Demander_Ecriture; + end Demander_Ecriture; + + procedure Terminer_Lecture is + begin + LectRedTask.Terminer_Lecture; + end Terminer_Lecture; + + procedure Terminer_Ecriture is + begin + LectRedTask.Terminer_Ecriture; + end Terminer_Ecriture; + +end LR.Synchro.Basique; diff --git a/TP6/src/lr-synchro-basique.ads b/TP6/src/lr-synchro-basique.ads new file mode 100644 index 0000000..03ccc41 --- /dev/null +++ b/TP6/src/lr-synchro-basique.ads @@ -0,0 +1,10 @@ +package LR.Synchro.Basique is + + function Nom_Strategie return String; + + procedure Demander_Lecture; + procedure Demander_Ecriture; + procedure Terminer_Lecture; + procedure Terminer_Ecriture; + +end LR.Synchro.Basique; diff --git a/TP6/src/lr-synchro-exclusion.adb b/TP6/src/lr-synchro-exclusion.adb new file mode 100644 index 0000000..8161445 --- /dev/null +++ b/TP6/src/lr-synchro-exclusion.adb @@ -0,0 +1,55 @@ +with Ada.Text_IO; use Ada.Text_IO; +with Ada.Exceptions; + +-- Version simple : exclusion mutuelle pour toutes les opérations +-- fournit une ossature pour l'approche "service" + +package body LR.Synchro.Exclusion is + + function Nom_Strategie return String is + begin + return "Exclusion mutuelle simple"; + end Nom_Strategie; + + task LectRedTask is + entry Demander_Lecture; + entry Demander_Ecriture; + entry Terminer_Lecture; + entry Terminer_Ecriture; + end LectRedTask; + + task body LectRedTask is + begin + loop + select + accept Demander_Lecture; accept Terminer_Lecture; + or + accept Demander_Ecriture; accept Terminer_Ecriture; + or + terminate; + end select; + -- une fois une opération acceptée, on accepte uniquement sa terminaison + end loop; + end LectRedTask; + + procedure Demander_Lecture is + begin + LectRedTask.Demander_Lecture; + end Demander_Lecture; + + procedure Demander_Ecriture is + begin + LectRedTask.Demander_Ecriture; + end Demander_Ecriture; + + procedure Terminer_Lecture is + begin + LectRedTask.Terminer_Lecture; + end Terminer_Lecture; + + procedure Terminer_Ecriture is + begin + LectRedTask.Terminer_Ecriture; + end Terminer_Ecriture; + +end LR.Synchro.Exclusion; \ No newline at end of file diff --git a/TP6/src/lr-synchro-exclusion.ads b/TP6/src/lr-synchro-exclusion.ads new file mode 100644 index 0000000..9f4af56 --- /dev/null +++ b/TP6/src/lr-synchro-exclusion.ads @@ -0,0 +1,10 @@ +package LR.Synchro.Exclusion is + + function Nom_Strategie return String; + + procedure Demander_Lecture; + procedure Demander_Ecriture; + procedure Terminer_Lecture; + procedure Terminer_Ecriture; + +end LR.Synchro.Exclusion; \ No newline at end of file diff --git a/TP6/src/lr-synchro-exclusion2.adb b/TP6/src/lr-synchro-exclusion2.adb new file mode 100644 index 0000000..16ca31b --- /dev/null +++ b/TP6/src/lr-synchro-exclusion2.adb @@ -0,0 +1,54 @@ +-- PQ (10/12) ; PM (10/14) + +with Ada.Text_IO; use Ada.Text_IO; +with Ada.Exceptions; + +-- Version radicale : exclusion mutuelle de tous +-- fournit une ossature pour l'approche "automate" +-- fournit un exemple de tâche proposant des entrées différentes de Demander_Lecture, ... + +package body LR.Synchro.Exclusion2 is + + function Nom_Strategie return String is + begin + return "Simple et Stupide, par exclusion mutuelle"; + end Nom_Strategie; + + task LectRedTask is + entry Demander; + entry Terminer; + end LectRedTask; + + task body LectRedTask is + begin + loop + accept Demander; + accept Terminer; + -- Comme promis : simple et stupide + end loop; + exception + when Error: others => + Put_Line("**** LectRedTask: exception: " & Ada.Exceptions.Exception_Information(Error)); + end LectRedTask; + + procedure Demander_Lecture is + begin + LectRedTask.Demander; + end Demander_Lecture; + + procedure Demander_Ecriture is + begin + LectRedTask.Demander; + end Demander_Ecriture; + + procedure Terminer_Lecture is + begin + LectRedTask.Terminer; + end Terminer_Lecture; + + procedure Terminer_Ecriture is + begin + LectRedTask.Terminer; + end Terminer_Ecriture; + +end LR.Synchro.Exclusion2; \ No newline at end of file diff --git a/TP6/src/lr-synchro-exclusion2.ads b/TP6/src/lr-synchro-exclusion2.ads new file mode 100644 index 0000000..2602ca7 --- /dev/null +++ b/TP6/src/lr-synchro-exclusion2.ads @@ -0,0 +1,10 @@ +package LR.Synchro.Exclusion2 is + + function Nom_Strategie return String; + + procedure Demander_Lecture; + procedure Demander_Ecriture; + procedure Terminer_Lecture; + procedure Terminer_Ecriture; + +end LR.Synchro.Exclusion2; \ No newline at end of file diff --git a/TP6/src/lr-synchro.adb b/TP6/src/lr-synchro.adb new file mode 100644 index 0000000..63ab0f6 --- /dev/null +++ b/TP6/src/lr-synchro.adb @@ -0,0 +1,17 @@ +with LR.Synchro.Exclusion; -- XXXX + +package body LR.Synchro is + + package Synchro renames LR.Synchro.Exclusion; -- XXXX + + function Nom_Strategie return String renames Synchro.Nom_Strategie; + + procedure Demander_Lecture renames Synchro.Demander_Lecture; + + procedure Demander_Ecriture renames Synchro.Demander_Ecriture; + + procedure Terminer_Lecture renames Synchro.Terminer_Lecture; + + procedure Terminer_Ecriture renames Synchro.Terminer_Ecriture; + +end LR.Synchro; \ No newline at end of file diff --git a/TP6/src/lr-synchro.ads b/TP6/src/lr-synchro.ads new file mode 100644 index 0000000..26172a5 --- /dev/null +++ b/TP6/src/lr-synchro.ads @@ -0,0 +1,10 @@ +package LR.Synchro is + + function Nom_Strategie return String; + + procedure Demander_Lecture; + procedure Demander_Ecriture; + procedure Terminer_Lecture; + procedure Terminer_Ecriture; + +end LR.Synchro; \ No newline at end of file diff --git a/TP6/src/lr-tasks.adb b/TP6/src/lr-tasks.adb new file mode 100644 index 0000000..c547f92 --- /dev/null +++ b/TP6/src/lr-tasks.adb @@ -0,0 +1,87 @@ +-- Time-stamp: <22 oct 2012 09:38 queinnec@enseeiht.fr> + +with Ada.Text_IO; use Ada.Text_IO; +with LR.Synchro; +with LR.Simu; +with LR.Affic; +with Ada.Numerics.Discrete_Random; +with Ada.Exceptions; + +package body LR.Tasks is + + package Rand_Int is new Ada.Numerics.Discrete_Random(Positive); + Generator : Rand_Int.Generator; + + -- Chaque lecteur lit entre MinDelayLit et MaxDelayLit secondes, + -- pense entre MinDelayLRien et MaxDelayLRien (les deux bornes incluses). + -- Chaque rédacteur écrit entre MinDelayEcrit et MaxDelayEcrit secondes, et + -- pense entre MinDelayERien et MaxDelayERien (les deux bornes incluses). + MinDelayLit : constant Positive := 5; + MaxDelayLit : constant Positive := 20; + MinDelayEcrit : constant Positive := 2; + MaxDelayEcrit : constant Positive := 10; + MinDelayLRien : Positive := MinDelayLit; + MaxDelayLRien : Positive := MaxDelayLit; + MinDelayERien : Positive := MinDelayEcrit; + MaxDelayERien : Positive := MaxDelayEcrit; + + -- Suspend la simulation de la tâche pendant [bi..bs] unités + procedure Random_Sleep(No: Proc_Id; Bi: Positive; Bs: Positive) is + Duree : Positive; + begin + Duree := (Rand_Int.Random(Generator) mod (Bs - Bi + 1)) + Bi; + LR.Simu.Sleep(No, Duree); + end Random_Sleep; + + task body Lecteur is + Id: Proc_Id; + begin + accept Init(MonId: Proc_Id) do Id:=MonId; end Init; + loop + Random_Sleep(Id, MinDelayLRien, MaxDelayLRien); + + Put_Line (Proc_Id'Image(Id) & " Calling DL"); + Lr.Affic.Changer_Etat(Id, LR.Affic.Lecteur_Demande); + LR.Synchro.Demander_Lecture; + + Put_Line (Proc_Id'Image(Id) & " Got DL, sleeping"); + Lr.Affic.Changer_Etat(Id, LR.Affic.Lecteur_Lit); + Random_Sleep(Id, MinDelayLit, MaxDelayLit); + + Put_Line (Proc_Id'Image(Id) & " Calling TL"); + LR.Synchro.Terminer_Lecture; + Lr.Affic.Changer_Etat(Id, LR.Affic.Lecteur_Rien); + end loop; + exception + when Error: others => + Put_Line("**** Tâche Lecteur: exception: " & Ada.Exceptions.Exception_Information(Error)); + end Lecteur; + + task body Redacteur is + Id: Proc_Id; + begin + accept Init(MonId: Proc_Id) do Id:=MonId; end Init; + loop + Random_Sleep(Id, MinDelayERien, MaxDelayERien); + + Put_Line (Proc_Id'Image(Id) & " Calling DR"); + LR.Affic.Changer_Etat(Id, LR.Affic.Redacteur_Demande); + LR.Synchro.Demander_Ecriture; + + Put_Line (Proc_Id'Image(Id) & " Got DR, sleeping"); + LR.Affic.Changer_Etat(Id, LR.Affic.Redacteur_Ecrit); + + Random_Sleep(Id, MinDelayEcrit, MaxDelayEcrit); + + Put_Line (Proc_Id'Image(Id) & " Calling TR"); + LR.Synchro.Terminer_Ecriture; + LR.Affic.Changer_Etat(Id, LR.Affic.Redacteur_Rien); + end loop; + exception + when Error: others => + Put_Line("**** LectRedTask: exception: " & Ada.Exceptions.Exception_Information(Error)); + end Redacteur; + +begin + Rand_Int.Reset(Generator); +end LR.Tasks; diff --git a/TP6/src/lr-tasks.ads b/TP6/src/lr-tasks.ads new file mode 100644 index 0000000..6c55d78 --- /dev/null +++ b/TP6/src/lr-tasks.ads @@ -0,0 +1,21 @@ +-- Time-stamp: <01 oct 2012 15:37 queinnec@enseeiht.fr> + +-- Tâches clients : lecteurs, rédacteurs +package LR.Tasks is + + MAXPROCS : constant Integer := 30; + + subtype Proc_Id is Positive range 1..MAXPROCS; + + task type Lecteur is + entry Init(MonId: Proc_Id); + end Lecteur; + + task type Redacteur is + entry Init(MonId: Proc_Id); + end Redacteur; + + type Lecteur_Access is access Lecteur; + type Redacteur_Access is access Redacteur; + +end LR.Tasks; diff --git a/TP6/src/lr.ads b/TP6/src/lr.ads new file mode 100644 index 0000000..bbb19ca --- /dev/null +++ b/TP6/src/lr.ads @@ -0,0 +1,2 @@ +package LR is +end LR; \ No newline at end of file diff --git a/TP6/src/mkstrategie b/TP6/src/mkstrategie new file mode 100644 index 0000000..6558764 --- /dev/null +++ b/TP6/src/mkstrategie @@ -0,0 +1,131 @@ +#!/bin/sh +# 11/19 + +actuelle=vrai +if [ $# -gt 1 ]; then + echo 'usage : mkstrategie [-i]' + exit 1 +elif [ $# -eq 1 ]; then # menu interactif + if [ $1 != '-i' ]; then + echo 'usage : mkstrategie [-i]' + exit 1 + else + cible="" + for f in *; do + if expr $f : 'lr-synchro-[^.]*\.ads' >/dev/null; then + cible=$cible$(expr $f : 'lr-synchro-\([^.]*\)\.ads')" " + fi + done + + if [ -z "$cible" ]; then + echo 'Pas de paquetage conforme.' + echo 'Les paquetages décrivant une stratégie doivent avoir une spécification' \ + 'nommée "lr-synchro-.ads".' + echo 'Le script mkstrategie et les paquetages décrivant les stratégies doivent' \ + "être dans le même répertoire, avec les différents paquetages de l'application." + echo 'Le répertoire contenant le script mkstrategie doit au moins contenir' \ + "la définition et l'implantation de la stratégie Exclusion mutuelle" \ + '(fichiers "lr-synchro-exclusion.adb" et "lr-synchro-exclusion.ads").' + else + reponse=999999 + nbpaq=$(echo $cible | wc -w) # nombre de paquetages + nbpaq=$(expr $nbpaq + 2) + while [ $reponse -gt $nbpaq ]; do + liste=$cible + echo 'Choisir une stratégie :' + echo '-----------------------' + echo ' 0) Pas de choix (Abandon)' + echo " 1) Création d'une nouvelle stratégie..." + echo ' 2) Stratégie actuelle ('$(cat lr-synchro.adb | + grep 'with LR\.Synchro\.' | head -1 | cut -f 3 -d . | cut -f 1 -d \;)')' + i=3 + while [ $i -le $nbpaq ]; do + strategie=$(echo $liste | cut -f 1 -d " ") + liste=$(echo $liste | cut -f 2- -d " ") + echo " $i) "$strategie + i=$(expr $i + 1) + done + read reponse + if [ -z "$reponse" ] || expr $reponse : '[^0-9]*' >/dev/null; then + reponse=999999 + fi + done + case $reponse in + 0) + echo "Abandon : aucune modification n'a été faite." + exit 2 + ;; + 1) + reponse="" + while [ -z "$reponse" ]; do + echo 'Nom de la nouvelle stratégie (q pour abandonner)' + read reponse + if [ -n "$reponse" ]; then + alerte="" + if expr "X$reponse" : 'X[Qq].*' >/dev/null; then + echo "Abandon : aucune modification n'a été faite." + exit 3 + else + message="Créer une nouvelle stratégie de nom "$reponse" (o/n) ?" + nomfic=lr-synchro-$(echo $reponse | tr '[:upper:]' '[:lower:]').ads + if [ -e $nomfic ]; then + alerte='--> ** Attention ** Une stratégie de même nom existe déjà.\n' + alerte=$alerte'--> Elle sera **écrasée** par la stratégie créée' + fi + fi + rep=c + until expr "X$rep" : 'X[ONon].*' >/dev/null; do + echo "$alerte" + echo $message + read rep + done + if expr "$rep" : '[Nn].*' >/dev/null; then + reponse="" + fi + fi + done + cible=$reponse + radical=$(echo $reponse | tr '[:upper:]' '[:lower:]') + echo "création d'un fichier de spécification (lr-synchro-"$radical".ads)" \ + "pour la stratégie "$reponse + cp lr-synchro.ads lr-synchro-$radical.ads + sed -i.old s/"\(package LR.Synchro\)\( *is\)"/"\1\.$reponse\2"/ lr-synchro-$radical.ads + sed -i.old s/"\(end LR.Synchro\)\(;\)"/"\1\.$reponse\2"/ lr-synchro-$radical.ads + rm lr-synchro-$radical.ads.old + # supprime la version d'origine sauvegardée par sed -i.old + # (pas de sed --in-place sans sauvegarde sous FreeBSD/MacOS) + echo "création d'un fichier d'implémentation (lr-synchro-"$radical".adb)" \ + "pour la stratégie "$reponse + cp lr-synchro-exclusion.adb lr-synchro-$radical.adb + sed -i.old s/"\(package body LR.Synchro.\)\([^ ]*\)\( *is\)"/"\1$reponse\3"/ lr-synchro-$radical.adb + sed -i.old s/"\(end LR.Synchro.\)\([^;]*\)\(;\)"/"\1$reponse\3"/ lr-synchro-$radical.adb + msg="Stratégie : $reponse" + sed -i.old s/'\([^"]*"\)\(Exclusion mutuelle simple\)\(.*$\)'/"\1$msg\3"/ lr-synchro-$radical.adb + rm lr-synchro-$radical.adb.old + actuelle=faux + ;; + 2) ;; + *) + i=$(expr $reponse - 2) + cible=$(echo $cible | cut -f $i -d " ") + actuelle=faux + ;; + esac + fi + fi +fi + +# edition +if [ $actuelle = 'vrai' ]; then + cible=$(cat lr-synchro.adb | head -1 | cut -f 3 -d . | cut -f 1 -d \;) +else + radical=$(echo $cible | tr '[:upper:]' '[:lower:]') + nom=$(cat lr-synchro-"$radical".ads | grep "package LR.Synchro." | cut -f 3 -d . | cut -f 1 -d ' ') + echo "Edition du fichier lr-synchro.adb pour la stratégie "$nom + sed -i.old s/"\(LR\.Synchro\.\)\([^;]*\)\(;.*-- XXXX\)"/"\1$nom\3"/ lr-synchro.adb + rm lr-synchro.adb.old +fi + +# compilation +echo "compilation pour $cible" +gprbuild build.gpr diff --git a/TP7/README.html b/TP7/README.html new file mode 100644 index 0000000..de6ef62 --- /dev/null +++ b/TP7/README.html @@ -0,0 +1,40 @@ + + + + + + + + + + +

Transactions et mémoire transactionnelle

+

Cette archive contient

+
    +
  • une classe Simulator qui correspond au point d'entrée du simulateur de mémoire transactionnelle. Elle contient la méthode main.
  • +
  • 2 paquetages tm et simulation, qui contiennent respectivement les classes des éléments de mémoire transactionnelle et les classes des différentes simulations.
  • +
  • un Makefile pour compiler le code source. Cette approche est recommandée par rapport à l'utilisation d'un IDE comme Eclipse.
  • +
  • un dossier scenarios qui contient des exemples de scénarios d'accès à une mémoire transactionnelle. +
      +
    • scenario0 est destiné (uniquement) au simulateur en mode interpréteur (pas à pas)
    • +
    • scenario1, scenario2 et scenario3 sont destinés (uniquement) au simulateur en mode "simulation complète"
    • +
  • +
+

Utilisation du simulateur en mode "Interpréteur (pas à pas)"

+
    +
  • Compilation : make
  • +
  • Exécution : make shell
  • +
  • Aide (dans le simulateur) : help
  • +
  • Lancement d'un scénario (dans le simulateur) : run <nom scénario>
  • +
+

Le fichier scenario0 correspond au premier scénario de la section 3 du sujet, pour le protocole PP.
+Note : <nom scénario> correspond au chemin d'accès au fichier.

+

Utilisation du simulateur en mode "Simulation complète"

+
    +
  • Compilation : make
  • +
  • Exécution : make simu SCEN=<nom_scénario>
  • +
+

Les fichiers scenario1, scenario2 et scenario3 correspondent aux scénarios proposés dans la section 4 du sujet.
+Note : <nom scénario> correspond au chemin d'accès au fichier.

+ + diff --git a/TP7/README.md b/TP7/README.md new file mode 100644 index 0000000..0f57f01 --- /dev/null +++ b/TP7/README.md @@ -0,0 +1,30 @@ +Transactions et mémoire transactionnelle +======================== + +Cette archive contient + +- une classe `Simulator` qui correspond au point d'entrée du simulateur de mémoire transactionnelle. Elle contient la méthode main. +- 2 paquetages `tm` et `simulation`, qui contiennent respectivement les classes des éléments de mémoire transactionnelle et les classes des différentes simulations. +- un Makefile pour compiler le code source. **Cette approche est recommandée par rapport à l'utilisation d'un IDE comme Eclipse**. +- un dossier `scenarios` qui contient des exemples de scénarios d'accès à une mémoire transactionnelle. + * `scenario0` est destiné (uniquement) au simulateur en mode interpréteur (pas à pas) + * `scenario1`, `scenario2` et `scenario3` sont destinés (uniquement) au simulateur en mode "simulation complète" + +Utilisation du simulateur en mode "Interpréteur (pas à pas)" +--------------------- +- *Compilation* : `make` +- *Exécution* : `make shell` +- *Aide (dans le simulateur)* : `help` +- *Lancement d'un scénario (dans le simulateur)* : `run ` + +Le fichier `scenario0` correspond au premier scénario de la section 3 du sujet, pour le protocole PP. +*Note* : `` correspond au *chemin d'accès* au fichier. + +Utilisation du simulateur en mode "Simulation complète" +--------------------- +- *Compilation* : `make` +- *Exécution* : `make simu SCEN=` + +Les fichiers `scenario1`, `scenario2` et `scenario3` correspondent +aux scénarios proposés dans la section 4 du sujet. +*Note* : `` correspond au *chemin d'accès* au fichier. diff --git a/TP7/TpTransactions-1.pdf b/TP7/TpTransactions-1.pdf new file mode 100644 index 0000000..eaa8e03 Binary files /dev/null and b/TP7/TpTransactions-1.pdf differ diff --git a/TP7/reponses.md b/TP7/reponses.md new file mode 100644 index 0000000..3226d64 --- /dev/null +++ b/TP7/reponses.md @@ -0,0 +1,231 @@ +## Scénario 1 + +### Quelles seront les transactions validées, bloquées ou abandonnées ? + +#### TMPP + +- Transaction T1 annulée (ft1 == 0), car il essaie de read y à la date 5 alors que celui-ci est lock par T2 à la date 4. +- Transaction T2 annulée (ft2 == 0), car il essaie de write z à la date 7 alors que celui-ci est lock par T3 à la date 6. +- Transaction T3 validée (ft3 == 1), T1 et T2 ayant été annulés, T3 n'a pas de problème pour terminer sa transaction. + +#### TMPC + +- Transaction T1 validée (ft1 == 1) +- Transaction T2 validée (ft2 == 1) +- Transaction T3 annulée (ft3 == 0), car T3 la valeur de z est modifié entre son read par T3 à la date 6 et son commit date 16, par T2 à la date 7. + +### TM2PL + +- Transaction T1 validée (ft2 == 1) +- Transaction T2 validée (ft2 == 1) +- Transaction T3 validée (ft3 == 1) + +Il n'y a pas d'interblocages, donc la politique de verouillage à deux phases fait se terminer toutes les transactions de l'exemple. + +### Quelles seront les valeurs de variables affichées par la commande list ? + +#### TMPP + +``` +T_objets : +ft1 0 +ft3 1 +ft2 0 +x 3 +y 0 +z 0 +``` + +#### TMPC + +``` +T_objets : +ft1 1 +ft3 0 +ft2 1 +x 0 +y 1 +z 2 +``` + +#### TM2PL + +``` +T_objets : +ft1 1 +ft3 0 +ft2 1 +x 0 +y 1 +z 2 +``` + +## Scénario 2 + +### Quelles seront les transactions validées, bloquées ou abandonnées ? + +#### TMPP + +Transaction T1 validée (ft1 == 1), T2 et T3 ayant été annulés, T3 n'a pas de problème pour terminer sa transaction. +Transaction T2 annulée (ft2 == 0), car il essaie de write z à la date 7 alors que celui-ci est lock par T3 à la date 6. +Transaction T3 annulée (ft3 == 0), car il essaie de write x à la date 10 alors que celui-ci est lock par T1 à la date 5. + +#### TMPC + +- Transaction T1 validée (ft1 == 1). +- Transaction T2 validée (ft2 == 1). +- Transaction T3 annulée (ft3 == 0), car la valeur de z est modifié entre son read par T3 à la date 6 et son commit date 16, par T2 à la date 7. + +### TM2PL + +- Transaction T1 bloqué (ft2 == 0) +- Transaction T2 bloqué (ft2 == 0) +- Transaction T3 bloqué (ft3 == 0) + +Il y a interblocages. + +### Quelles seront les valeurs de variables affichées par la commande list ? + +#### TMPP + +``` +T_objets : +ft1 1 +ft3 0 +ft2 0 +x 0 +y 0 +z 0 +``` + +#### TMPC + +``` +T_objets : +ft1 1 +ft3 0 +ft2 1 +x 0 +y 1 +z 2 +``` + +#### TM2PL + +``` +T_objets : +ft1 0 +ft3 0 +ft2 0 +x 0 +y 1 +z 0 +``` + +## Illustrer la limite de la propagation directe sur un scénario simple, interprété par le shell. + +``` +init TM2PL (x,0) (ft1,0) (ft2,0) (ft3,0) +new T1 ; new T2 ; new T3 + +T1 write x 1 +T2 read x +T1 write x 2 +T3 read x + +T1 write ft1 1 ; T1 commit +T2 write ft2 1 ; T2 commit +T3 write ft3 1 ; T3 commit +``` + +À la fin de ces transaction, à cause de la propagation directe, T2 et T3 n'aurons pas lu la même valeur de x, et cela peut causer des comportements inattendus. + +# Évaluation de protocoles + +## Commenter les scénarios fournis + +Scénario 1 +``` +Transaction t1 validée +0 unités de temps perdues +44 unités de temps attendues +26 unités de temps utiles +Transaction t2 validée +0 unités de temps perdues +19 unités de temps attendues +31 unités de temps utiles +Transaction t3 validée +0 unités de temps perdues +0 unités de temps attendues +33 unités de temps utiles + +Temps total : 70 +Temps optimal : 33 +Temps séquentiel : 90 +``` + +Scénario 2 +``` +Transaction t1 validée +0 unités de temps perdues +0 unités de temps attendues +26 unités de temps utiles +Transaction t2 validée +0 unités de temps perdues +0 unités de temps attendues +31 unités de temps utiles +Transaction t3 annulée +33 unités de temps perdues +0 unités de temps attendues +0 unités de temps utiles + +Temps total : 33 +Temps optimal : 33 +Temps séquentiel : 90 +``` + +Scénario 3 +``` +Transaction t1 validée +0 unités de temps perdues +0 unités de temps attendues +26 unités de temps utiles +Transaction t2 validée +0 unités de temps perdues +0 unités de temps attendues +31 unités de temps utiles +Transaction t3 validée +33 unités de temps perdues +0 unités de temps attendues +33 unités de temps utiles + +Temps total : 66 +Temps optimal : 33 +Temps séquentiel : 90 +``` + +Le scénario 2 est plus rapide que le scénario 1, puisque ses transactions n'attendent pas les autres, cependant la transaction T3 échoue. +Le scénario 3 est plus rapide que le scénario 1, ceci est dû à l'utilsation de super-transactions, en effet la transaction T3 du scénario 2 est rejoué à la fin des transaction T1 et T2. + +## Définir des scénarios permettant de caractériser les situations favorables à chacune des politiques + +TODO + +# Mise en œuvre des protocoles de contrôle de concurrence + +## Indiquer (et justifier) pour chacun de ces protocoles + +### A +- optimiste, les modifications sont écrites sur la mémoire principale et "undo" si la transaction abort +- l'algorithme est optimiste, il peut ne pas assurer la sérialisabilité +- différée, les locks ne sont libérés qu'à la toute fin +- Il y a risque d'interblocage +- Il y a risque de rejet en cascade + +### B +- pessimiste, les modificatison des transactions sont effectuées dans une mémoire à part, et transférée vers la mémoire principale lors du commit. +- l'algorithme est pessimiste, il assure la sérialisabilité +- différée, puisque le protocole est pessimiste +- Il n'y a pas de risque d'interblocage +- Il n'y a pas risque de rejet en cascade + diff --git a/TP7/src/Makefile b/TP7/src/Makefile new file mode 100644 index 0000000..1dcf8ff --- /dev/null +++ b/TP7/src/Makefile @@ -0,0 +1,16 @@ +JAVAFILES= $(wildcard *.java) $(wildcard tm/*.java) $(wildcard simulation/*.java) +CLASSFILES= $(wildcard *.class) $(wildcard tm/*.class) $(wildcard simulation/*.class) + +all: $(JAVAFILES) + javac -encoding UTF8 $(JAVAFILES) + +shell: + java -Dfile.encoding=UTF8 Simulator + +simu: + java -Dfile.encoding=UTF8 Simulator $(SCEN) + +clean: + rm -f $(CLASSFILES) + +.PHONY: all run clean diff --git a/TP7/src/Simulator.java b/TP7/src/Simulator.java new file mode 100644 index 0000000..bffe359 --- /dev/null +++ b/TP7/src/Simulator.java @@ -0,0 +1,42 @@ +import java.lang.reflect.InvocationTargetException; + +import java.io.IOException; +import java.io.FileNotFoundException; +import java.io.File; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.NoSuchElementException; + +import simulation.*; +import tm.*; + +// Main class running the transactional memory. +public class Simulator { + + public static void main(String[] args) + throws InterruptedException, IOException, NoSuchMethodException, + InvocationTargetException { + + if (args.length > 1) { + System.err.println("L'application se lance soit sans argument" + +" (pour une exécution en mode interprété, pas à pas)," + +" soit avec un seul argument " + +"(le fichier de scénario à traiter, en mode simulation)."); + System.exit(1); + } + + AbstractSimulation simu; + + if (args.length == 1) { + File spec = new File(args[0]); + FullSimulationBuilder builder = new FullSimulationBuilder(); + simu = builder.build(spec, false); + } else { + simu = new StepByStepSimulation(); + } + + simu.run(); + } + +} diff --git a/TP7/src/random_exercise.java b/TP7/src/random_exercise.java new file mode 100644 index 0000000..70baf6c --- /dev/null +++ b/TP7/src/random_exercise.java @@ -0,0 +1,67 @@ +public class random_exercise { + + static Integer compteur = 0; + + public static void main(String[] args) throws InterruptedException { + + Thread thread1 = new Thread() { + @Override + public void run() { + while (compteur < 10) { + synchronized (compteur) { + compteur++; + } + try { + Thread.sleep(100); + System.out.println(compteur); + + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + }; + + Thread thread2 = new Thread() { + @Override + public void run() { + while (compteur < 20) { + synchronized (compteur) { + compteur++; + } + try { + Thread.sleep(100); + System.out.println(compteur); + + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + }; + + Thread thread3 = new Thread() { + @Override + public void run() { + int old_counter = 0; + while (true) { + synchronized (compteur) { + if (compteur % 2 == 1 && old_counter != compteur) { + System.out.println("c " + compteur); + old_counter = compteur; + } + } + } + } + }; + + thread3.start(); + thread1.start(); + thread1.join(); + thread2.start(); + thread2.join(); + System.out.println(compteur); + + } + +} diff --git a/TP7/src/scenarios/scenario0 b/TP7/src/scenarios/scenario0 new file mode 100644 index 0000000..f2860cb --- /dev/null +++ b/TP7/src/scenarios/scenario0 @@ -0,0 +1,17 @@ +init TM2PL (x,0) (y,0) (z,0) (ft1,0) (ft2,0) (ft3,0) +new T1 +new T2 +new T3 +T2 write y 1 +T1 read x +T3 read z +T2 write z 2 +T1 read y +T2 read x +T3 write x 3 +T1 write ft1 1 +T1 commit +T2 write ft2 1 +T2 commit +T3 write ft3 1 +T3 commit diff --git a/TP7/src/scenarios/scenario1 b/TP7/src/scenarios/scenario1 new file mode 100644 index 0000000..fadb293 --- /dev/null +++ b/TP7/src/scenarios/scenario1 @@ -0,0 +1,4 @@ +TM2PL x y z +t1 T : process 5 ; read y ; process 10 ; read x ; process 8 ; commit +t2 T : process 2 ; write y; process 10 ; write z; process 5 ; read x ; process 10 ; commit +t3 T : process 8 ; read z ; process 12 ; write x ; process 10 ; commit diff --git a/TP7/src/scenarios/scenario2 b/TP7/src/scenarios/scenario2 new file mode 100644 index 0000000..b243b00 --- /dev/null +++ b/TP7/src/scenarios/scenario2 @@ -0,0 +1,4 @@ +TMPC x y z +t1 T : process 5 ; read y ; process 10 ; read x ; process 8 ; commit +t2 T : process 2 ; write y; process 10 ; write z; process 5 ; read x ; process 10 ; commit +t3 T : process 8 ; read z ; process 12 ; write x; process 10 ; commit diff --git a/TP7/src/scenarios/scenario3 b/TP7/src/scenarios/scenario3 new file mode 100644 index 0000000..38ebcc3 --- /dev/null +++ b/TP7/src/scenarios/scenario3 @@ -0,0 +1,4 @@ +TMPC x y z +t1 S : process 5 ; read y ; process 10 ; read x ; process 8 ; commit +t2 S :process 2 ; write y ; process 10 ; write z ; process 5 ; read x ; process 10 ; commit +t3 S : process 8 ; read z ; process 12 ; write x ; process 10 ; commit diff --git a/TP7/src/scenarios/scenario_tm2pl.stdout b/TP7/src/scenarios/scenario_tm2pl.stdout new file mode 100644 index 0000000..7462863 --- /dev/null +++ b/TP7/src/scenarios/scenario_tm2pl.stdout @@ -0,0 +1,53 @@ +java -Dfile.encoding=UTF8 Simulator +> Date 1 +T2 : write y 1 +T2 : y <- 1 +Date 2 +T1 : read y +T1 : y -> 0 +Date 3 +T3 : read z +T3 : z -> 0 +Date 4 +T2 : write z 2 +T2 : z <- 2 +Date 5 +T1 : read x +T1 : x -> 0 +Date 6 +T2 : read x +T2 : x -> 0 +Date 7 +T3 : write x 3 +T3 : x <- 3 +Date 8 +T1 : write ft1 1 +T1 : ft1 <- 1 +Date 9 +T1 : commit +T1 : T1 validée +Date 10 +T2 : write ft2 1 +T2 : ft2 <- 1 +Date 11 +T2 : commit +T2 : T2 validée +Date 12 +T3 : write ft3 1 +T3 : ft3 <- 1 +Date 13 +T3 : commit +T3 : T3 annulée + +Transaction T1 validée +Transaction T2 validée +Transaction T3 annulée + +T_objets : +ft1 1 +ft3 0 +ft2 1 +x 0 +y 1 +z 2 +> \ No newline at end of file diff --git a/TP7/src/scenarios/scenario_tm2pl_2.stdout b/TP7/src/scenarios/scenario_tm2pl_2.stdout new file mode 100644 index 0000000..f924417 --- /dev/null +++ b/TP7/src/scenarios/scenario_tm2pl_2.stdout @@ -0,0 +1,32 @@ +java -Dfile.encoding=UTF8 Simulator +> Date 1 +T2 : write y 1 +T2 : y <- 1 +Date 2 +T1 : read x +T1 : x -> 0 +Date 3 +T3 : read z +T3 : z -> 0 +Date 4 +T2 : write z 2 +T2 bloquée +Date 5 +T1 : read y +T1 bloquée +T2 bloquée +Date 7 +T1 bloquée +T2 bloquée +T3 : write x 3 +T3 bloquée + + +T_objets : +ft1 0 +ft3 0 +ft2 0 +x 0 +y 1 +z 0 +> \ No newline at end of file diff --git a/TP7/src/scenarios/scenario_tmpc.stdout b/TP7/src/scenarios/scenario_tmpc.stdout new file mode 100644 index 0000000..7462863 --- /dev/null +++ b/TP7/src/scenarios/scenario_tmpc.stdout @@ -0,0 +1,53 @@ +java -Dfile.encoding=UTF8 Simulator +> Date 1 +T2 : write y 1 +T2 : y <- 1 +Date 2 +T1 : read y +T1 : y -> 0 +Date 3 +T3 : read z +T3 : z -> 0 +Date 4 +T2 : write z 2 +T2 : z <- 2 +Date 5 +T1 : read x +T1 : x -> 0 +Date 6 +T2 : read x +T2 : x -> 0 +Date 7 +T3 : write x 3 +T3 : x <- 3 +Date 8 +T1 : write ft1 1 +T1 : ft1 <- 1 +Date 9 +T1 : commit +T1 : T1 validée +Date 10 +T2 : write ft2 1 +T2 : ft2 <- 1 +Date 11 +T2 : commit +T2 : T2 validée +Date 12 +T3 : write ft3 1 +T3 : ft3 <- 1 +Date 13 +T3 : commit +T3 : T3 annulée + +Transaction T1 validée +Transaction T2 validée +Transaction T3 annulée + +T_objets : +ft1 1 +ft3 0 +ft2 1 +x 0 +y 1 +z 2 +> \ No newline at end of file diff --git a/TP7/src/scenarios/scenario_tmpc_2.stdout b/TP7/src/scenarios/scenario_tmpc_2.stdout new file mode 100644 index 0000000..7bbb1dd --- /dev/null +++ b/TP7/src/scenarios/scenario_tmpc_2.stdout @@ -0,0 +1,53 @@ +java -Dfile.encoding=UTF8 Simulator +> Date 1 +T2 : write y 1 +T2 : y <- 1 +Date 2 +T1 : read x +T1 : x -> 0 +Date 3 +T3 : read z +T3 : z -> 0 +Date 4 +T2 : write z 2 +T2 : z <- 2 +Date 5 +T1 : read y +T1 : y -> 0 +Date 6 +T2 : read x +T2 : x -> 0 +Date 7 +T3 : write x 3 +T3 : x <- 3 +Date 8 +T1 : write ft1 1 +T1 : ft1 <- 1 +Date 9 +T1 : commit +T1 : T1 validée +Date 10 +T2 : write ft2 1 +T2 : ft2 <- 1 +Date 11 +T2 : commit +T2 : T2 validée +Date 12 +T3 : write ft3 1 +T3 : ft3 <- 1 +Date 13 +T3 : commit +T3 : T3 annulée + +Transaction T1 validée +Transaction T2 validée +Transaction T3 annulée + +T_objets : +ft1 1 +ft3 0 +ft2 1 +x 0 +y 1 +z 2 +> \ No newline at end of file diff --git a/TP7/src/scenarios/scenario_tmpp.stdout b/TP7/src/scenarios/scenario_tmpp.stdout new file mode 100644 index 0000000..acc663e --- /dev/null +++ b/TP7/src/scenarios/scenario_tmpp.stdout @@ -0,0 +1,46 @@ +java -Dfile.encoding=UTF8 Simulator +> Date 1 +T2 : write y 1 +T2 : y <- 1 +Date 2 +Annulation des modifications de T1. +Annulation terminée pour T1. +T1 : read y +T1 : T1 annulée +Date 3 +T3 : read z +T3 : z -> 0 +Date 4 +Annulation des modifications de T2. +y 0 +Annulation terminée pour T2. +T2 : write z 2 +T2 : T2 annulée +Date 5 +Date 6 +Date 7 +T3 : write x 3 +T3 : x <- 3 +Date 8 +Date 9 +Date 10 +Date 11 +Date 12 +T3 : write ft3 1 +T3 : ft3 <- 1 +Date 13 +T3 : commit +T3 : T3 validée + +Transaction T1 annulée +Transaction T2 annulée +Transaction T3 validée + +T_objets : +ft1 0 +ft3 1 +ft2 0 +x 3 +y 0 +z 0 +> \ No newline at end of file diff --git a/TP7/src/scenarios/scenario_tmpp_2.stdout b/TP7/src/scenarios/scenario_tmpp_2.stdout new file mode 100644 index 0000000..1a25864 --- /dev/null +++ b/TP7/src/scenarios/scenario_tmpp_2.stdout @@ -0,0 +1,48 @@ +java -Dfile.encoding=UTF8 Simulator +> Date 1 +T2 : write y 1 +T2 : y <- 1 +Date 2 +T1 : read x +T1 : x -> 0 +Date 3 +T3 : read z +T3 : z -> 0 +Date 4 +Annulation des modifications de T2. +y 0 +Annulation terminée pour T2. +T2 : write z 2 +T2 : T2 annulée +Date 5 +T1 : read y +T1 : y -> 0 +Date 6 +Date 7 +Annulation des modifications de T3. +Annulation terminée pour T3. +T3 : write x 3 +T3 : T3 annulée +Date 8 +T1 : write ft1 1 +T1 : ft1 <- 1 +Date 9 +T1 : commit +T1 : T1 validée +Date 10 +Date 11 +Date 12 +Date 13 + +Transaction T1 validée +Transaction T2 annulée +Transaction T3 annulée + +T_objets : +ft1 1 +ft3 0 +ft2 0 +x 0 +y 0 +z 0 +> \ No newline at end of file diff --git a/TP7/src/simulation/AbstractSimulation.java b/TP7/src/simulation/AbstractSimulation.java new file mode 100644 index 0000000..34d2f76 --- /dev/null +++ b/TP7/src/simulation/AbstractSimulation.java @@ -0,0 +1,41 @@ +package simulation; + +import java.lang.reflect.InvocationTargetException; + +import java.util.NoSuchElementException; +import java.io.IOException; +import java.io.FileNotFoundException; + +import java.util.Map; +import java.util.HashMap; +import java.util.List; +import java.util.Arrays; +import java.util.ArrayList; + +import tm.*; + +// Classe abstraite : objets et méthodes d'une simulation +public abstract class AbstractSimulation { + + // Mémoire transactionnelle + protected AbstractTM tm; + + // Map associant un identifiant aux données conservées pour chaque transaction + protected Map transactions; + + // Fonction principale : lance la simulation + public abstract void run() + throws InterruptedException, IOException, + ClassCastException, + NumberFormatException, NoSuchMethodException, + InvocationTargetException, NoSuchElementException; + + // Fonction utilitaire qui enlève les éléments vides d'un tableau de String + // (pour l'analyse syntaxique des lignes de commande). + public static String[] clean(String[] dirty) { + List clean = + new ArrayList(Arrays.asList(dirty)); + clean.removeAll(Arrays.asList("")); + return clean.toArray(new String[clean.size()]); + } +} diff --git a/TP7/src/simulation/FullSimulation.java b/TP7/src/simulation/FullSimulation.java new file mode 100644 index 0000000..211243d --- /dev/null +++ b/TP7/src/simulation/FullSimulation.java @@ -0,0 +1,310 @@ +package simulation; + +import java.lang.reflect.InvocationTargetException; + +import java.io.IOException; +import java.io.FileNotFoundException; +import java.io.File; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.NoSuchElementException; + +import java.util.Collections; +import java.util.Set; +import java.util.HashSet; +import java.util.Map; +import java.util.HashMap; +import java.util.List; +import java.util.LinkedList; +import java.util.Arrays; +import java.util.ArrayList; + +import java.util.Random; + +import java.util.Scanner; + +import tm.*; + +// Implémentation d'AbstractSimulation réalisant une simulation globale +public class FullSimulation extends AbstractSimulation { + + // Horloge globale + private int clock; + + // Booléen : les valeurs lues ou écrites doivent-elles être prises en compte ? + private boolean meaningful; + + // Accès restant à effectuer en cas de blocage. + private Map> afterBlocking; + + // Echéancier : associe une date aux différentes actions des différentes transactions. + private Map> planning; + + // L'essentiel de l'initialisation est réalisée par la classe FullSimulationBuilder. + public FullSimulation() { + this.clock = 0; + this.afterBlocking = new HashMap>(); + } + + public void setMeaningful(boolean meaningful) { + this.meaningful = meaningful; + } + + public void setTM(AbstractTM tm) { + this.tm = tm; + } + + public void setPlanning(Map> planning) { + this.planning = planning; + } + + public void setTransactions(Map transactions) { + this.transactions = transactions; + } + + public void run() + throws InterruptedException, IOException, FileNotFoundException { + + int min = 0; + String output = null; + Set toWait = null; + boolean stillRunning = true; + // Epuiser l'échéancier jusqu'à la dernière opération. + while (!this.planning.isEmpty() && stillRunning) + { + min = Collections.min(this.planning.keySet()); + this.clock = min; + System.out.println("Date "+min); + for (String transaction : this.planning.get(min).keySet()) { + this.simulate(transaction, + this.planning.get(min) + .get(transaction)); + } + Thread.sleep(100); + toWait = + new HashSet(this.planning.get(min).keySet()); + toWait.addAll(this.afterBlocking.keySet()); + for (String transaction : toWait) { + if(this.planning.get(min).keySet().contains(transaction)) { + output = transaction + " : " + String.join(" ", + this.planning.get(min) + .get(transaction)); + System.out.println(output); + } + output = this.transactions.get(transaction).responses.poll(); + if (output != null) { + System.out.println(transaction+" : "+output); + } else { + System.out.println(transaction+" bloquée"); + } + this.updateState(transaction, output); + } + this.planning.remove(min); + + } + + this.display(); + stillRunning = false; + for (String name : this.transactions.keySet()) { + if (this.transactions.get(name).state == TransactionState.RUNNING){ + stillRunning = true; + break; + } + } + + } + + + // Envoie les instructions à la mémoire transactionnelle. + private void simulate(String transaction, String[] command) + throws InterruptedException, IOException, FileNotFoundException + { + String output = null; + if (this.transactions.get(transaction).state != TransactionState.ABORTED) { + this.transactions.get(transaction).instructions.put(command); + } + } + + + + // Met à jour l'état de la transaction. + private void updateState(String transaction, String output) { + if (output == null && + this.transactions.get(transaction).state != TransactionState.BLOCKED) + { + this.block(transaction); + } else if (output != null && + this.transactions.get(transaction).state == TransactionState.BLOCKED) + { + this.unblock(transaction); + } else if (output != null && output.contains("relancée")) { + this.relaunch(transaction); + } else if (output != null && output.contains("annulée")){ + this.abort(transaction); + } else if (output != null && output.contains("validée")) { + this.commit(transaction); + } + } + + + + // Retire du planning les instructions restantes d'une transaction bloquée. + private void block(String transaction) { + HashSet emptyTimes = new HashSet(); + + this.afterBlocking.put(transaction, new HashMap()); + for (Integer time : this.planning.keySet()) { + if (time != this.clock && + this.planning.get(time).containsKey(transaction)) + { + this.afterBlocking.get(transaction) + .put(time - this.clock, + this.planning.get(time).remove(transaction)); + if (this.planning.get(time).isEmpty()) { + emptyTimes.add(time); + } + } + } + + for (Integer time : emptyTimes) { + this.planning.remove(time); + } + + this.transactions.get(transaction).state = TransactionState.BLOCKED; + this.transactions.get(transaction).whenBlocked = this.clock; + } + + + + // Remet dans l'échéancier les instructions restantes d'une transaction débloquée. + private void unblock(String transaction) { + + for (Integer time : this.afterBlocking.get(transaction).keySet()) { + int newTime = this.clock+time; + if (this.planning.containsKey(newTime)) { + this.planning.get(newTime) + .put(transaction, + this.afterBlocking.get(transaction).get(time)); + } else { + this.planning.put(newTime, new HashMap()); + this.planning.get(newTime) + .put(transaction, + this.afterBlocking.get(transaction).get(time)); + } + } + + this.transactions.get(transaction).state = TransactionState.RUNNING; + this.transactions.get(transaction).blocked += + (this.clock - this.transactions.get(transaction).whenBlocked); + this.afterBlocking.remove(transaction); + } + + + + // Met à jour l'échéancier lorsqu'une superTransaction est relancée. + private void relaunch(String transaction) { + for (Integer time : this.planning.keySet()) { + if (time != this.clock) { + this.planning.get(time).remove(transaction); + } + } + + Map init = this.transactions.get(transaction).init; + for (Integer time : init.keySet()) { + int newTime = this.clock+time; + if (this.planning.containsKey(newTime)) { + this.planning.get(newTime) + .put(transaction, + init.get(time)); + } else { + this.planning.put(newTime, new HashMap()); + this.planning.get(newTime) + .put(transaction, + init.get(time)); + } + } + + this.transactions.get(transaction).useless = this.clock; + } + + + // Annule proprement une transaction. + private void abort(String transaction) { + for (Integer time : this.planning.keySet()) { + if (time != this.clock) { + this.planning.get(time).remove(transaction); + } + } + + this.transactions.get(transaction).state = TransactionState.ABORTED; + this.transactions.get(transaction).useless = this.clock; + } + + // Valide proprement une transaction. + private void commit(String transaction) { + this.transactions.get(transaction).state = TransactionState.COMMITTED; + this.transactions.get(transaction).useful = + this.clock- + (this.transactions.get(transaction).useless + +this.transactions.get(transaction).blocked); + } + + + + // Affiche les résultats de la simulation. + private void display() { + + String output = ""; + + int minTime = 0; + int seqTime = 0; + + System.out.print(System.lineSeparator()); + + for (String transaction : this.transactions.keySet()) { + if (this.transactions.get(transaction).state == + TransactionState.COMMITTED) { + System.out.println("Transaction "+transaction+" validée"); + } else if (this.transactions.get(transaction).state == + TransactionState.ABORTED) { + System.out.println("Transaction "+transaction+" annulée"); + } + + if (!(this.meaningful)) { + System.out.println( + this.transactions.get(transaction).useless+" unités de temps perdues"); + System.out.println( + this.transactions.get(transaction).blocked+" unités de temps attendues"); + System.out.println( + this.transactions.get(transaction).useful+" unités de temps utiles"); + + minTime = Math.max(minTime, + Collections.max(this.transactions + .get(transaction) + .init + .keySet())); + seqTime += Collections.max(this.transactions + .get(transaction) + .init + .keySet()); + } + } + + System.out.print(System.lineSeparator()); + + if (!meaningful) { + System.out.println("Temps total : "+this.clock); + System.out.println("Temps optimal : "+minTime); + System.out.println("Temps séquentiel : "+seqTime); + } else { + Map t_objects = this.tm.getTObjects(); + output = "T_objets :"; + for (String t_object : t_objects.keySet()) { + output = output+"\n"+t_object+" "+t_objects.get(t_object); + } + System.out.println(output); + } + + } +} diff --git a/TP7/src/simulation/FullSimulationBuilder.java b/TP7/src/simulation/FullSimulationBuilder.java new file mode 100644 index 0000000..830fc89 --- /dev/null +++ b/TP7/src/simulation/FullSimulationBuilder.java @@ -0,0 +1,252 @@ +package simulation; + +import java.lang.reflect.InvocationTargetException; + +import java.io.IOException; +import java.io.FileNotFoundException; +import java.io.File; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.NoSuchElementException; + +import java.util.Collections; +import java.util.Set; +import java.util.HashSet; +import java.util.Map; +import java.util.HashMap; +import java.util.List; +import java.util.LinkedList; +import java.util.Arrays; +import java.util.ArrayList; + +import java.util.Random; + +import java.util.Scanner; + +import tm.*; + +// Classe d'initialisation pour FullSimulation, à partir d'un fichier scénario. +public class FullSimulationBuilder { + + private FullSimulation simu; + private Integer offset; + + public FullSimulationBuilder() { + this.simu = new FullSimulation(); + this.offset = 1; + } + + public FullSimulation build(File spec, boolean meaningful) + throws NumberFormatException, NoSuchMethodException + { + //Création du Scanner. + Scanner scan = null; + try { + scan = new Scanner(spec); + } catch (FileNotFoundException e) { + System.err.println("Fichier scénario non trouvé."); + System.exit(1); + } + + + // Analyser la ligne d'initialisation. + String line = null; + try { + line = scan.nextLine(); + } catch (NoSuchElementException e) { + System.err.println("Fichier scénario vide."); + System.exit(1); + } + + AbstractTM tm = this.initializeTM(line, meaningful); + + // Analyse des scénarios pour chacune des transactions. + Map transactions = + new HashMap(); + Map> planning = + new HashMap>(); + Random rand = new Random(); + + while(scan.hasNextLine()) { + line = scan.nextLine(); + if (meaningful) { + this.parseCommandLine(line, tm, transactions, planning); + } else { + this.offset = 1; + this.parseTransLine(line, tm, transactions, planning, rand); + } + } + + // Build FullSimulation object. + this.simu.setTM(tm); + this.simu.setMeaningful(meaningful); + this.simu.setPlanning(planning); + this.simu.setTransactions(transactions); + + return this.simu; + } + + private AbstractTM initializeTM(String line, boolean meaningful){ + AbstractTM tm = null; + String[] splitLine = this.simu.clean(line.split(" ")); + + Map t_objects = new HashMap(); + + String nameTM = ""; + try { + if (meaningful) { + nameTM = "tm."+splitLine[1]; + Class classTM = + Class.forName(nameTM).asSubclass(AbstractTM.class); + Class[] classArgs = {Map.class}; + String buffer = null; + String[] buffers = null; + for (int i = 2; i < splitLine.length ; i++) { + buffer = splitLine[i]; + buffers = buffer.replaceAll("[()]", "").split(","); + t_objects.put(buffers[0],Integer.parseInt(buffers[1])); + } + tm = classTM.getDeclaredConstructor(classArgs) + .newInstance(t_objects); + } else { + nameTM = "tm."+splitLine[0]; + Class classTM = + Class.forName(nameTM).asSubclass(AbstractTM.class); + Class[] classArgs = {Map.class}; + + for (int i = 1; i < splitLine.length ; i++) { + t_objects.put(splitLine[i],0); + } + + tm = classTM.getDeclaredConstructor(classArgs) + .newInstance(t_objects); + } + } catch (ClassNotFoundException e) { + System.err.println("La classe "+nameTM+" n'existe pas."); + System.exit(1); + } catch (ClassCastException e) { + System.err.println("La classe "+nameTM+" n'hérite pas de AbstractTM."); + System.exit(1); + } catch (NoSuchMethodException e) { + System.err.println("La classe "+nameTM+" n'a pas de constructeur" + +" ayant Map en argument."); + System.exit(1); + } catch (InstantiationException e) { + System.err.println("La classe "+nameTM+" ne peut être instanciée."); + System.exit(1); + } catch (IllegalAccessException e) { + System.err.println("La classe "+nameTM+" ne peut être accédée."); + System.exit(1); + } catch (InvocationTargetException e) { + System.err.println("Le constructeur de la classe "+nameTM+"a levé" + +" l'exception: "+e.getMessage()); + System.exit(1); + } + + return tm; + } + + + private void parseCommandLine(String line, + AbstractTM tm, + Map transactions, + Map> planning) { + + String[] splitLine = this.simu.clean(line.split(" ")); + + if (splitLine[0].equals("new")) { + String name = splitLine[1]; + BlockingQueue instructions = new LinkedBlockingQueue(); + BlockingQueue responses = new LinkedBlockingQueue(); + AbstractTransaction trans; + trans = new Transaction(name, + tm, + instructions, + responses); + tm.newTransaction(trans); + transactions.put(name, + new TransactionObject(new HashMap(), + instructions, + responses)); + } else if (transactions.keySet().contains(splitLine[0])) { + String name = splitLine[0]; + + String[] command = Arrays.copyOfRange(splitLine, + 1, + splitLine.length); + + transactions.get(name).init.put(this.offset,command); + planning.put(this.offset, new HashMap()); + planning.get(this.offset).put(name,command); + this.offset++; + } else { + System.err.println("Erreur de syntaxe dans le fichier scénario. "); + + System.err.println(splitLine[0]); + System.err.println(splitLine[1]); + for (String transaction : transactions.keySet()) { + System.err.println(transaction); + } + + System.exit(1); + } + + } + + private void parseTransLine(String line, + AbstractTM tm, + Map transactions, + Map> planning, + Random rand) { + + String[] splitLine = line.split(":"); + String[] header = this.simu.clean(splitLine[0].split(" ")); + String name = header[0]; + String type = header[1]; + String[] commands = this.simu.clean(splitLine[1].split(";")); + String[] command = null; + + BlockingQueue instructions = new LinkedBlockingQueue(); + BlockingQueue responses = new LinkedBlockingQueue(); + + Map init = new HashMap(); + for (int i=0; i < commands.length; i++) { + command = this.simu.clean(commands[i].split(" ")); + + if (command[0].equals("process")) { + this.offset += Integer.parseInt(command[1]); + } else { + init.put(this.offset,command); + if (!planning.containsKey(this.offset)) + { + planning.put(this.offset, + new HashMap()); + } + planning.get(this.offset).put(name,command); + this.offset++; + } + } + + AbstractTransaction trans; + if (type.equals("S")) { + trans = new SuperTransaction(name, + tm, + instructions, + responses, + rand); + } else { + trans = new Transaction(name, + tm, + instructions, + responses, + rand); + } + + tm.newTransaction(trans); + transactions.put(name, + new TransactionObject(init, + instructions, + responses)); + } +} diff --git a/TP7/src/simulation/StepByStepSimulation.java b/TP7/src/simulation/StepByStepSimulation.java new file mode 100644 index 0000000..8961ddd --- /dev/null +++ b/TP7/src/simulation/StepByStepSimulation.java @@ -0,0 +1,265 @@ +package simulation; + +import java.lang.reflect.InvocationTargetException; + +import java.io.IOException; +import java.io.FileNotFoundException; +import java.io.File; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.NoSuchElementException; + +import java.util.Collections; +import java.util.Set; +import java.util.HashSet; +import java.util.Map; +import java.util.HashMap; +import java.util.List; +import java.util.LinkedList; +import java.util.Arrays; +import java.util.ArrayList; +import java.util.Random; +import java.util.Scanner; + +import tm.*; + +// Implémentation d'AbstractSimulation réalisant une simulation interprétée pas à pas. +public class StepByStepSimulation extends AbstractSimulation { + + // Ensemble des transactions bloquées. + private Set blocked; + + private boolean exit; + + public StepByStepSimulation() { + this.exit = false; + this.blocked = new HashSet(); + } + + public void run() + throws InterruptedException, IOException, + ClassCastException, + NumberFormatException, NoSuchMethodException, + InvocationTargetException, NoSuchElementException { + + Scanner scan = new Scanner(System.in); + String line; + String[] commands; + + while (!exit) { + System.out.print("> "); + line = scan.nextLine(); + commands = this.clean(line.split(" ")); + if (commands.length > 0) { + if (commands[0].equals("run")) { + FullSimulationBuilder builder = new FullSimulationBuilder(); + builder.build(new File(commands[1]),true).run(); + } else { + System.out.println(this.handle(commands)); + } + } + } + + System.exit(0); + } + + + // Vérifie la bonne forme des instructions et les envoie à la mémoire transactionnelle. + private String handle(String[] commands) + throws InterruptedException, IOException, FileNotFoundException { + String output = null; + switch (commands[0]) { + case "exit" : + this.exit = true; + output = "Arrêt de la mémoire transactionnelle."; + break; + case "init" : + if (commands.length < 3) { + output = "L'initialisation a la forme 'init (t_object,valeur)'."; + } else { + try { + Class classTM = + Class.forName("tm."+commands[1]).asSubclass(AbstractTM.class); + Class[] classArgs = {Map.class}; + Map t_objects = new HashMap(); + String buffer = null; + String[] buffers = null; + for (int i = 2; i < commands.length ; i++) { + buffer = commands[i]; + buffers = buffer.replaceAll("[()]", "").split(","); + t_objects.put(buffers[0],Integer.parseInt(buffers[1])); + } + tm = classTM.getDeclaredConstructor(classArgs) + .newInstance(t_objects); + this.transactions = new HashMap(); + this.blocked = + new HashSet(); + output = "Initialisation de la mémoire transactionnelle."; + } catch (ClassNotFoundException | ClassCastException e) { + output = + "Cette classe de mémoire transactionnelle n'existe pas.\n "; + output += "Les choix possibles sont : " + + Arrays.toString(this.findDerivedClasses("AbstractTM")); + } catch (NumberFormatException e) { + output = + "Les t_objets doivent contenir un entier."; + } catch (NoSuchMethodException | + InstantiationException | + IllegalAccessException | + InvocationTargetException e) { + output = "Erreur d'instanciation."; + } + } + break; + case "new" : + if (this.tm == null) { + output = + "La mémoire transactionnelle doit avoir été créée avant d'être utilisée."; + } else if (commands.length != 2) { + output = + "L'ajout de transaction doit être de la forme 'new '"; + } else { + BlockingQueue instructions = + new LinkedBlockingQueue(); + BlockingQueue responses = + new LinkedBlockingQueue(); + if (!tm.newTransaction(new Transaction( + commands[1], + this.tm, + instructions, + responses))) + { + output = "Nom déjà pris."; + } else { + output = "Transaction "+commands[1]+" créée."; + this.transactions.put( + commands[1], + new TransactionObject(new HashMap(), + instructions, + responses)); + } + } + break; + case "help" : + output = + "La liste des commandes disponibles est :\n"+ + " - 'init (t_object,valeur)' permet d'instancier une mémoire "+ + "transactionnelle de la classe donnée avec les noms d'objets "+ + "fournis. Choix possibles pour : "+ + Arrays.toString(this.findDerivedClasses("AbstractTM"))+".\n"+ + " - 'new ' permet de créer une transaction.\n"+ + " - 'list' permet de lister les transactions et les objets.\n"+ + " - ' read ' permet à une transaction "+ + "de lire l'entier stocké dans un objet.\n"+ + " - ' write ' permet "+ + "à une transaction d'écrire dans un objet.\n"+ + " - ' commit' permet à une transaction de valider.\n"+ + " - ' abort' permet à une transaction d'abandonner.\n"+ + " - 'run