This commit is contained in:
Laureηt 2023-06-21 20:19:26 +02:00
commit c64e8caa21
Signed by: Laurent
SSH key fingerprint: SHA256:kZEpW8cMJ54PDeCvOhzreNr4FSh6R13CMGH/POoO8DI
126 changed files with 10788 additions and 0 deletions

103
TP1/CC_README.html Normal file
View file

@ -0,0 +1,103 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta http-equiv="Content-Style-Type" content="text/css" />
<meta name="generator" content="pandoc" />
<title></title>
<style type="text/css">code{white-space: pre;}</style>
</head>
<body>
<h1 id="concurrence-et-cohérence">Concurrence et cohérence</h1>
<h2 id="objectifs">Objectifs</h2>
<ul>
<li>évaluer le gain, le coût et le contexte approprié à la mise en œuvre multi-activités d'un traitement</li>
<li>mettre en évidence les problèmes de cohérence induits par
<ul>
<li>l'exécution d'activités concurrentes</li>
<li>les mécanismes d'optimisation matériels (caches, pipelines) et logiciels (réordonnancement des instructions)</li>
</ul></li>
</ul>
<h2 id="préparation">Préparation</h2>
<p>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.</p>
<h3 id="les-activités-threads-java">Les activités (threads) Java</h3>
<p>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.</p>
<ul>
<li>la classe <strong><code>Thread</code></strong>, intégrée au langage (paquetage <code>java.lang</code>), permet de <em>définir</em> un processus (léger), ou thread au sein d'une application Java (JVM)</li>
<li>la classe Thread fournit (en particulier)
<ul>
<li>une méthode <strong><code>start</code></strong> qui permet de <em>lancer</em> l'instance de thread auquel elle est appliquée</li>
<li>une méthode <strong><code>join</code></strong> (<code>void join() throws InterruptedException</code>), qui permet d'attendre la terminaison l'instance de thread auquel elle est appliquée</li>
<li>une méthode de classe, <code>static Thread currentThread()</code> qui fournit la référence du thread en cours d'exécution</li>
<li>une méthode de classe, <code>static void sleep(long ms) throws InterruptedException</code> qui suspend le thread appelant pour une durée de <code>ms</code> millisecondes</li>
</ul></li>
<li>le constructeur de la classe <code>Thread</code> prend un paramètre d'une classe implémentant l'interface <strong><code>Runnable</code></strong>. Cette interface expose une méthode <strong><code>public void run()</code></strong>. La méthode <code>run()</code> définit le code qui sera exécuté par le thread correspondant.</li>
</ul>
<p><strong>Les planches 10 et 11 fournissent des exemples simples du schéma standard de création de threads</strong></p>
<h3 id="le-modèle-de-cohérence-java">Le modèle de cohérence Java</h3>
<p>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.</p>
<p>Cependant Java offre quelques outils de base pour faciliter la programmation multiactivités avec des variables partagées :</p>
<ul>
<li>le mot-clé <strong><code>volatile</code></strong> 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, mais 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 <code>volatile</code> inhibe le réordonnancement des instructions<br />
</li>
<li>le paquetage <code>java.util.concurrent.atomic</code> 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 <code>synchronized</code> (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.</li>
</ul>
<h3 id="les-verrous-java">Les verrous Java</h3>
<p>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.</p>
<p>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.</p>
<h4 id="principe">Principe</h4>
<p><strong>Tout objet</strong> Java peut être utilisé comme un verrou pour contrôler l'accès à une section critique.</p>
<h4 id="syntaxe">Syntaxe</h4>
<ul>
<li><p>Le mot-clé <strong><code>synchronized</code></strong> permet de définir très simplement une section critique contrôlée par (le verrou d')un objet <code>unObj</code>, avec la syntaxe suivante <strong><code>synchronized</code></strong> <code>(unObj) { section critique }</code></p></li>
<li><p>Le mot-clé <strong><code>synchronized</code></strong> 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.</p></li>
<li><p>Enfin, il est possible de qualifier des méthodes de classe (statiques) comme <strong><code>synchronized</code></strong>. Dans ce cas, le verrou est associé à la classe, non à ses instances.</p></li>
</ul>
<h3 id="autres-classes-méthodes-et-remarques-utiles">Autres classes, méthodes et remarques utiles</h3>
<ul>
<li>la classe <code>System</code> fournit deux méthodes, <code>System.nanoTime()</code> et <code>System.currentTimeMillis()</code> 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.</li>
<li>Le constructeur de la classe <code>Thread</code> peut prendre un paramètre de classe <code>String</code>, qui permet de donner un nom à l'instance de Thread créée. Le nom peut être accédé via les méthodes <code>getName()</code> et <code>setName()</code></li>
<li><p>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 : <code>export JAVA_TOOL_OPTIONS=-Dfile.encoding=UTF8</code>), ou encore de lancer la compilation avec l'option <code>-encoding UTF8</code>, ce qui donne ici :</p>
<pre><code>javac -encoding UTF8 *.java</code></pre></li>
<li>l'interpréteur Java (commande <code>java</code>) founit deux options qui ne seront pas forcément nécessaires ici, mais qui peuvent être utiles dans un contexte d'évaluation de performances :</li>
<li><code>-Xint</code> force le passage en mode interprété pur (pas de compilation à la volée, ni par conséquent d'optimisation)</li>
<li><p><code>-Xprof</code> fournit des statistiques sur les temps d'exécution des threads.</p></li>
</ul>
<h2 id="exercices">Exercices</h2>
<h3 id="efficacité-de-la-parallélisation">Efficacité de la parallélisation</h3>
<p>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 <code>IncrMes</code> 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.</p>
<p>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).</p>
<ul>
<li>Quel résultat « idéal » peut-on a priori espérer ?</li>
<li><p>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 :) )</p>
<ul>
<li>Expliquez les différences observées entre le temps mesuré et le temps attendu.</li>
<li>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)</li>
</ul></li>
</ul>
<h3 id="coût-de-la-cohérence">Coût de la cohérence</h3>
<p>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.)</p>
<ul>
<li>Quelles seront <em>a priori</em> les valeurs affichées dans le cas où il n'y a pas préemption du processeur entre threads ?</li>
<li>Quelles seront <em>a priori</em> les valeurs affichées dans le cas où la gestion des activités partage le temps processeur par quantum de temps entre threads ?</li>
<li>Quelle est la politique effectivement suivie par la JVM utilisée pour le test ?</li>
<li>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.</li>
<li><p>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 <code>synchronized</code>, associé à un objet global quelconque. (Déclarer par exemple un attribut <code>static Object mutex = new Object();</code> 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.</p>
<ul>
<li>en plaçant uniquement l'incrémentation de la boucle interne dans le bloc <code>synchronized</code></li>
<li>en plaçant la boucle interne dans le bloc <code>synchronized</code></li>
</ul></li>
<li>La correction du résultat est-elle garantie a priori si l'on utilise un objet de la classe <code>java.util.concurrent.atomic.AtomicLong</code> pour le compteur ? Argumenter, puis vérifier cet a priori. Evaluer le coût de l'utilisation de ce mécanisme</li>
<li>La correction du résultat est-elle garantie a priori si l'on déclare le compteur comme <code>volatile</code> ? Argumenter, puis vérifier cet a priori. Evaluer le coût de l'utilisation de ce mécanisme.</li>
<li><p>Conclure globalement sur les conditions d'utilisation (ou pas) de ces différents mécanismes.</p></li>
</ul>
<h3 id="supplément">Supplément</h3>
<p>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 <code>IncrMes</code>, et n'introduit rien de nouveau.</p>
<h2 id="tester-les-performances-dapplications-concurrentes-en-java-quelques-remarques-pratiques">Tester les performances d'applications concurrentes en Java : quelques remarques pratiques</h2>
<ul>
<li>sources de perturbation : cache, compilateur à la volée, ramasse miettes et optimiseur, charge de l'environnement (matériel, réseau) -&gt; 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).</li>
<li>tester sur des volumes de données significatifs</li>
<li>connaître le nombre de processeurs réels disponibles</li>
</ul>
</body>
</html>

183
TP1/CC_README.md Normal file
View file

@ -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

189
TP1/IncrMes.java Normal file
View file

@ -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 <Nb activités> <durée pause (ms)> <nbMesures>,"
+"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);
}
}

49
TP1/PCA.java Normal file
View file

@ -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);
}
}
}

BIN
TP1/fig.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

19
TP1/reponses.md Normal file
View file

@ -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 ™

1
TP1/result_nosync.txt Normal file
View file

@ -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

1
TP1/result_seq.txt Normal file
View file

@ -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

5
TP1/script.sh Normal file
View file

@ -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

63
TP2/Mutex/MX_README.md Normal file
View file

@ -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 laccès exclusif à `unObj` et bloque lactivité appelante
en attente dun 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(); } <opérations à réaliser une fois la condition vérifiée > }`
## 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...

46
TP2/Mutex/Peterson.java Normal file
View file

@ -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 ");
}
}

170
TP2/Mutex/ProdConso.java Normal file
View file

@ -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 <nbProd> <nbConso> <nbCases>");
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
}
}

43
TP2/Mutex/TAS.java Normal file
View file

@ -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 ");
}
}

5
TP2/Mutex/reponses.md Normal file
View file

@ -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.

7
TP3/EtatFourchette.java Normal file
View file

@ -0,0 +1,7 @@
// Time-stamp: <24 fév 2010 09:39 queinnec@enseeiht.fr>
public enum EtatFourchette {
Table,
AssietteGauche,
AssietteDroite;
}

8
TP3/EtatPhilosophe.java Normal file
View file

@ -0,0 +1,8 @@
// Time-stamp: <24 fév 2010 09:39 queinnec@enseeiht.fr>
public enum EtatPhilosophe {
Pense,
Demande,
Mange;
}

126
TP3/IHMArgs.java Normal file
View file

@ -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<String>(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<String> lesChoix = new LinkedList<String>();
// 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]);
}
}

93
TP3/IHMChoixNombre.java Normal file
View file

@ -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<Integer,JLabel> labelTable = new Hashtable<Integer,JLabel>();
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);
}
}

88
TP3/IHMParametres.java Normal file
View file

@ -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<String>(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);
}
}

466
TP3/IHMPhilo.java Normal file
View file

@ -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",
"<html><head></head><body><br>"+
"Les Philosophes et les Spaghettis"+
"<br>------------------------<br><br>"+
"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.<br><br>"+
"Symboles :"+
"<ul>"+
"<li>un disque plein indique un philosophe mangeant ;"+
"<ul>"+
"<li>un disque noir indique une situation normale ;"+
"<li>un disque rouge signale une incohérence <i>possible</i> ;"+
"</ul>"+
"<li>un cercle indique un philosophe demandeur ;"+
"<li>aucun symbole indique un philosophe penseur ;"+
"</ul>"+
"Actions :"+
"<ul>"+
"<li>en cliquant dans l'assiette d'un philosophe, vous pouvez forcer une transition: penser -&gt; demander, ou manger -&gt; penser (la transition demander -&gt; manger reste du ressort de la synchronisation);"+
"<li>pause permet de suspendre le temps de la simulation. Les actions forcées sont par contre toujours possibles;"+
"<li>vous pouvez régler la vitesse de la simulation avec l'échelle du bas."+
"</ul>"+
"</body></html>");
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<Integer,JLabel> labelTable = new Hashtable<Integer,JLabel>();
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);
}
}

180
TP3/Main.java Normal file
View file

@ -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 <implantation> <nb_philo>");
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);
}
}
}

66
TP3/PhiloSem.java Normal file
View file

@ -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";
}
}

63
TP3/PhiloSem2.java Normal file
View file

@ -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é";
}
}

83
TP3/PhiloSem3.java Normal file
View file

@ -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";
}
}

View file

@ -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);
}
}
}

82
TP3/README.html Normal file
View file

@ -0,0 +1,82 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta http-equiv="Content-Style-Type" content="text/css" />
<meta name="generator" content="pandoc" />
<title></title>
<style type="text/css">code{white-space: pre;}</style>
</head>
<body>
<h1 id="problème-des-philosophes">Problème des philosophes</h1>
<h2 id="énoncé">Énoncé</h2>
<p>N philosophes sont autour d'une table. Il y a une assiette par philosophe, et <em>une</em> fourchette entre chaque assiette. Pour manger, un philosophe doit utiliser les deux fourchettes adjacentes à son assiette (et celles-là seulement).</p>
<p>Un philosophe peut être dans l'état :</p>
<ul>
<li>penseur : il n'utilise pas de fourchettes ;</li>
<li>mangeur : il utilise les deux fourchettes adjacentes ; aucun de ses voisins ne peut manger ;</li>
<li>demandeur : il souhaite manger mais ne dispose pas des deux fourchettes.</li>
</ul>
<p>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.</p>
<h2 id="code-fourni">Code fourni</h2>
<ul>
<li><code>StrategiePhilo.java</code> : interface de la synchronisation entre philosophes.</li>
<li><code>PhiloSem.java</code> : une implantation de cette interface.</li>
<li><code>ProcessusPhilosophe.java</code> : code d'un philosophe.</li>
<li><code>Main.java</code> : programme principal. Définit aussi les <code>PhiloDroite(i)</code>, <code>PhiloGauche(i)</code>, <code>FourchetteGauche(i)</code>, <code>FourchetteDroite(i)</code>.</li>
<li><code>EtatFourchette.java</code> : définition des constantes pour fourchette placée sur la table, l'assiette gauche, l'assiette droite.</li>
<li><code>EtatPhilosophe.java</code> : définition des constantes pour philosophe penseur, demandeur ou mangeur.</li>
<li><code>IHM*.java</code> : interface utilisateur.</li>
<li><p><code>Synchro/Simulateur.java</code> : le simulateur de temps.</p></li>
<li><p>Compilation:<br />
<code>javac *.java Synchro/*.java</code></p></li>
<li><p>Exécution:<br />
<code>java Main</code><br />
<code>java Main PhiloSem 10</code><br />
(classe implantant l'interface StrategiePhilo) (nb de philosophes)</p>
<p>Le bouton d'aide de la fenêtre affichée par l'application en présente les fonctionnalités.</p></li>
</ul>
<h2 id="la-classe-semaphore">La classe Semaphore</h2>
<p>La plateforme Java fournit la classe <code>java.util.concurrent.Semaphore</code> qui propose une implantation des sémaphores généraux, avec notamment :</p>
<ul>
<li><p>un constructeur prenant un paramètre entier, correspondant à la valeur initiale du sémaphore. Un second paramètre <em>optionnel</em> booléen, qui permet de préciser si le sémaphore créé est FIFO. Par défaut, les sémaphores de la classe <code>java.util.concurrent.Semaphore</code> ne sont pas FIFO.<br />
Par exemple : <code>s=new Semaphore(5,true)</code> crée un sémaphore FIFO de valeur initiale 5.</p></li>
<li>une méthode <code>acquire()</code>, qui correspond à l'opération <code>P()</code></li>
<li><p>une méthode <code>release()</code>, qui correspond à l'opération <code>V()</code></p></li>
</ul>
<p>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 <code>P()</code>non bloquants (<code>tryAcquire()</code>), de demander à augmenter ou diminuer la valeur du sémaphore de plusieurs unités en une seule opération (<code>acquire(k)</code>, <code>release(j)</code>), 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...</p>
<h2 id="à-faire">À faire</h2>
<h3 id="première-approche-les-fourchettes-sont-des-ressources-critiques">Première approche : les fourchettes sont des ressources critiques</h3>
<p>=&gt; associer un sémaphore à chacune des fourchettes</p>
<ul>
<li>Implanter une version de base, où tous les philosophes commencent par prendre leur fourchette de droite avant de prendre leur fourchette de gauche.</li>
<li>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.</li>
<li>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.</li>
</ul>
<h3 id="seconde-approche-contrôler-la-progression-dun-philosophe-en-fonction-de-létat-de-ses-voisins.">Seconde approche : contrôler la progression d'un philosophe en fonction de l'état de ses voisins.</h3>
<p>=&gt; 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 :</p>
<ul>
<li>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;</li>
<li>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.</li>
</ul>
<p>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.</p>
<h4 id="equité">Equité</h4>
<p>Montrer (en exhibant un scénario avec 4 ou 5 philosophes) que cette solution « optimale » peut conduire à la famine d'un philosophe.</p>
<p>Imaginer une solution gérant une priorité entre les philosophes permettant de résoudre ce problème. Etudier</p>
<ul>
<li>le degré de parallélisme dans le pire des cas. Comparer avec la solution optimale.</li>
<li>l'attente maximum pour un philosophe demandeur (en termes nombre de philosophes servis avant le philosophe demandeur)</li>
<li>les limites éventuelles de la solution proposée.</li>
</ul>
<h2 id="indications">Indications</h2>
<ul>
<li><p><code>PhiloSem.java</code> 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.</p></li>
<li><p>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...)</p></li>
<li><p>Utiliser <code>Main.java</code> pour les numéros (<code>Main.FourchetteGauche</code> / <code>Main.FourchetteDroite</code> / <code>Main.PhiloGauche</code> / <code>Main.PhiloDroite</code>).</p></li>
<li><p>(Optionnel) Pour pour poser la fourchette n°f sur l'assiette à <em>sa</em> droite, à <em>sa</em> gauche ou sur la table, utiliser</p>
<pre><code>IHMPhilo.poser (f, EtatFourchette.AssietteDroite);
IHMPhilo.poser (f, EtatFourchette.AssietteGauche);
IHMPhilo.poser (f, EtatFourchette.Table);</code></pre></li>
</ul>
</body>
</html>

137
TP3/README.md Normal file
View file

@ -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);

17
TP3/StrategiePhilo.java Normal file
View file

@ -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();
}

27
TP3/Synchro/Assert.java Normal file
View file

@ -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 <code>AssertionViolation</code> si la
* condition <code>b</code> est fausse.
*/
public static void check (boolean b) {
if (! b)
throw new AssertionViolation ();
}
/** Lève l'exception <code>AssertionViolation</code> avec le message
* <code>msg</code> si la condition <code>b</code> est fausse.
*/
public static void check (String msg, boolean b) {
if (! b)
throw new AssertionViolation (msg);
}
}

View file

@ -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 <code>Error</code>, 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);}
}

258
TP3/Synchro/Simulateur.java Normal file
View file

@ -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<Proc> procs;
private int lastId = 0;
private ThreadLocal<Proc> 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<Proc> 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<Proc>();
this.procself = new ThreadLocal<Proc>() {
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
* <code>noproc</code>, 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 ...
}
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 <code>bi</code> et <code>bs</code>. */
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
* <code>no</code>, pour une durée aléatoire comprise entre
<code>bi</code> et <code>bs</code>. */
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 <code>noproc</code>. Sans effet si
* le processus ne dort pas. */
public void wakeup (int noproc)
{
synchronized (this) {
Iterator<Proc> 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 à <code>suspendTime</code> sans
alternance de <code>resumeTime</code> 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
* <code>suspendTime</code>. */
public void resumeTime ()
{
synchronized (this) {
running = previous_running;
if (running)
this.notify();
}
}
}

138
TP4/IHMArgs.java Normal file
View file

@ -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<String>(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<String> lesChoix = new LinkedList<String>();
// 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]);
}
}

93
TP4/IHMChoixNombre.java Normal file
View file

@ -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<Integer,JLabel> labelTable = new Hashtable<Integer,JLabel>();
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);
}
}

607
TP4/IHMLectRed.java Normal file
View file

@ -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<ObjetGraphique> lesLecteurs = new LinkedList<ObjetGraphique>();
private List<ObjetGraphique> lesRedacteurs = new LinkedList<ObjetGraphique>();
private Map<ProcId,ObjetGraphique> lesProcessus = new HashMap<ProcId,ObjetGraphique>();
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 <code>ens</code> qui soit dans l'état
* <code>etat</code>.
*/
private ObjetGraphique chercherDormeur (List<ObjetGraphique> 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",
"<html><head></head><body><br>"+
"Lecteurs/Rédacteurs<br>-------------------<br>"+
"\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.<br><br>"+
"Interprétation du dessin :"+
"<ul><li>les disques noirs sont les rédacteurs, et les cercles noirs sont les lecteurs;"+
"<li>un processus dans le rectangle central possède l'accès;"+
"<li>un processus à proximité du rectangle demande l'accès."+
"</ul>"+
"Actions :"+
"<ul><li>en cliquant dans le rectangle, vous forcez une fin d'utilisation;</li>"+
"<li>en cliquant côté lecteur, vous forcez une demande de lecture;</li>"+
"<li>en cliquant côté rédacteur, vous forcez une demande d'écriture;</li>"+
"<li>pause permet de suspendre le temps de la simulation."+
" Les actions forcées sont par contre toujours possibles;</li>"+
"<li>vous pouvez régler la vitesse de la simulation avec l'échelle du bas.</li>"+
"</ul>"+
"</body></html>");
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<Integer,JLabel> labelTable = new Hashtable<Integer,JLabel>();
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);
}
}

119
TP4/IHMParametres.java Normal file
View file

@ -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<String> (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<String>(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();
}
}

10
TP4/LectRed.java Normal file
View file

@ -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 ();
}

10
TP4/LectRedEtat.java Normal file
View file

@ -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
}

82
TP4/LectRed_FIFO.java Normal file
View file

@ -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.";
}
}

View file

@ -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.";
}
}

View file

@ -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.";
}
}

208
TP4/Main.java Normal file
View file

@ -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 <implantation> <nb_lect> <nb_red>");
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);
}
}
}

57
TP4/ProcessusLecteur.java Normal file
View file

@ -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 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();
}
}
}

View file

@ -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 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();
}
}
}

32
TP4/README.html Normal file
View file

@ -0,0 +1,32 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta http-equiv="Content-Style-Type" content="text/css" />
<meta name="generator" content="pandoc" />
<title></title>
<style type="text/css">code{white-space: pre;}</style>
</head>
<body>
<h1 id="problème-des-lecteursrédacteurs">Problème des lecteurs/rédacteurs</h1>
<h2 id="objectif">Objectif</h2>
<p>Écrire des implantations de LectRed.java.</p>
<p>Stratégies à implanter</p>
<ul>
<li><p>priorité aux rédacteurs ou aux lecteurs</p></li>
<li><p>équitable (absence de famine que ce soient des lecteurs ou des rédacteurs)</p></li>
</ul>
<h2 id="compilation">Compilation</h2>
<p><code>javac *.java Synchro/*.java</code></p>
<h2 id="exécution">Exécution</h2>
<p><code>java Main</code></p>
<ul>
<li>Le programme trouve automatiquement toutes les implantations disponibles dans le répertoire (par réflexivité), et en particulier les nouvelles implantations rajoutées.</li>
<li>Le bouton d'aide de la fenêtre affichée par l'application en présente les fonctionnalités.</li>
</ul>
<p>Il est par ailleurs également possible de lancer une implantation particulière par :</p>
<p><code>java Main &lt;l'implantation écrite&gt; &lt;nb lecteurs&gt; &lt;nb rédacteurs&gt;</code></p>
<p>par exemple:</p>
<p><code>java Main MonImplantation 6 4</code></p>
</body>
</html>

77
TP4/README.md Normal file
View file

@ -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 daide de la fenêtre affichée par lapplication 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`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 daide de la fenêtre affichée par lapplication 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`

27
TP4/Synchro/Assert.java Normal file
View file

@ -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 <code>AssertionViolation</code> si la
* condition <code>b</code> est fausse.
*/
public static void check (boolean b) {
if (! b)
throw new AssertionViolation ();
}
/** Lève l'exception <code>AssertionViolation</code> avec le message
* <code>msg</code> si la condition <code>b</code> est fausse.
*/
public static void check (String msg, boolean b) {
if (! b)
throw new AssertionViolation (msg);
}
}

View file

@ -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 <code>Error</code>, 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);}
}

61
TP4/Synchro/ProcId.java Normal file
View file

@ -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<ProcId> 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<ProcId>() {
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;
}
}

238
TP4/Synchro/Simulateur.java Normal file
View file

@ -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<Proc> 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<Proc> 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<Proc>();
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
* <code>noproc</code>, 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 <code>bi</code> et <code>bs</code>. */
public void sleep (int bi, int bs)
{
if (bi <= bs)
sleep (random.nextInt(bs - bi + 1) + bi);
}
/** Interrompt le sommeil du processus <code>noproc</code>. Sans effet si
* le processus ne dort pas. */
private void wakeupAndDie (ProcId id, boolean mustDie)
{
synchronized (this) {
Iterator<Proc> 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 <code>noproc</code>. 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 à <code>suspendTime</code> sans
alternance de <code>resumeTime</code> 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
* <code>suspendTime</code>. */
public void resumeTime ()
{
synchronized (this) {
running = previous_running;
if (running)
this.notify();
}
}
}

8
TP4/Synchro/Suicide.java Normal file
View file

@ -0,0 +1,8 @@
// Time-stamp: <02 mai 2013 10:16 queinnec@enseeiht.fr>
package Synchro;
@SuppressWarnings("serial")
public class Suicide extends RuntimeException
{
}

46
TP4/rappel_moniteur.md Normal file
View file

@ -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.

19
TP4/reponses.md Normal file
View file

@ -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)

8
TP5/Contenu.md Normal file
View file

@ -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)

411
TP5/README.html Normal file
View file

@ -0,0 +1,411 @@
<!DOCTYPE html
PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta http-equiv="Content-Style-Type" content="text/css" />
<meta name="generator" content="pandoc" />
<title></title>
<style type="text/css">
code {
white-space: pre;
}
</style>
</head>
<body>
<h1 id="parallélisme-régulé">Parallélisme régulé</h1>
<h2 id="objectifs">Objectifs</h2>
<ul>
<li>gérer le parallélisme à gros grain</li>
<li>paralléliser un algorithme par décomposition en sous-tâches</li>
<li>connaître les services d'exécution de la plateforme Java</li>
</ul>
<h2 id="prérequis">Prérequis</h2>
<p>Vous devez savoir parfaitement comment définir une activité (Thread) en Java, comment lancer une activité, et
comment attendre sa terminaison.</p>
<p>** 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**</p>
<p>Vous aurez vraisemblablement besoin lors de ce TP d'utiliser les méthodes de classe suivantes de la classe
<code>Thread</code> :</p>
<ul>
<li><code>static Thread currentThread()</code> qui fournit la référence du thread en cours d'exécution</li>
<li><code>static void sleep(long ms) throws InterruptedException</code> qui suspend le thread appelant pour une
durée de <code>ms</code> millisecondes</li>
</ul>
<p>Enfin, vous aurez sans doute aussi besoin de deux méthodes de classe de la classe <code>System</code> :
<code>System.nanoTime()</code> et <code>System.currentTimeMillis()</code> 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.</p>
<h2 id="préparation-services-de-régulation-des-activités-en-java">Préparation : services de régulation des activités
en Java</h2>
<p><em>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 <a
href="https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/package-summary.html">documentation
Java en ligne</a> pour la syntaxe et les détails techniques.</em></p>
<p>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
<code>volatile</code>, classes atomiques...) et du type d'attente (blocs <code>synchronized</code>, verrous,
attente active).</p>
<p>La plateforme Java fournit dans ses dernières versions la classe <code>Executor</code>, destinée à séparer la
gestion des activités des aspects purement applicatifs. Le principe est qu'un objet de la classe
<code>Executor</code> (« exécuteur ») fournit un <em>service</em> de gestion et d'ordonnancement d'activités,
auquel on soumet des <em>tâches</em> à 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</p>
<ul>
<li>de simplifier la tâche du programmeur, puisqu'il n'a plus à gérer le démarrage des activités, ni leur
ordonnancement</li>
<li>d'adapter le nombre d'activités exécutées à la charge et au nombre de processeurs physiques disponibles</li>
</ul>
<p>Le paquetage <code>java.util.concurrent</code> définit 3 interfaces pour les exécuteurs :</p>
<ul>
<li><code>Executor</code>, qui fournit une méthode <code>execute</code>, permettant de soumettre une tâche
<code>Runnable</code>.</li>
<li><code>ExecutorService</code>, qui étend <code>Executor</code>, avec une méthode <code>submit</code>,
permettant de soumettre une tâche <code>Callable</code> et renvoyant un objet <code>Future</code>, lequel
permet de récupérer la valeur de retour de la tâche <code>Callable</code> soumise. Un
<code>ExecutorService</code> permet en outre de soumettre des ensembles de tâches <code>Callable</code>, et
de gérer la terminaison de l'exécuteur.</li>
<li><code>ScheduledExecutorService</code>, qui étend <code>ExecutorService</code> avec des méthodes permettant
de spécifier l'ordonnancement des tâches soumises.</li>
</ul>
<p>Le paquetage <code>java.util.concurrent</code> 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.</p>
<p>Les exécuteurs fournis par le paquetage <code>java.util.concurrent</code> sont de deux sortes :</p>
<h3 id="pools-de-threads">Pools de threads</h3>
<p>La classe <code>java.util.concurrent.Executors</code> fournit des méthodes permettant de créer des pools de
threads implantant <code>ExecutorService</code> avec un nombre d'ouvriers fixe -- méthode
<code>newFixedThreadPool</code> --, variable (adaptable) -- méthode <code>newCachedThreadPool</code>) ou
permettant une régulation par vol de tâches (voir cours) (méthode <code>newWorkStealingPool</code>). Une
variante implantant <code>ScheduledExecutorService</code> est proposée pour chacune de ces méthodes, afin de
permettre d'intervenir sur l'ordonnancement des tâches. Enfin, les classes
<code>java.util.concurrent.ThreadPoolExecutor</code> et
<code>java.util.concurrent.ScheduledThreadPoolExecutor</code> proposent encore davantage d'options sur la
paramétrage et la supervision de l'ordonnancement.</p>
<p>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 :</p>
<ul>
<li>é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,</li>
<li>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,</li>
<li>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.</li>
</ul>
<p>D'une manière générale,</p>
<ul>
<li>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.
<ul>
<li><em>Note : l'appel de la méthode <code>Runtime.getRuntime().availableProcessors()</code> fournit le
nombre de processeurs disponibles pour la JVM courante.</em></li>
</ul>
</li>
<li>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.</li>
</ul>
<h5 id="classes-et-méthodes-utiles">Classes et méthodes utiles</h5>
<ul>
<li>la classe <code>java.util.concurrent.Executors</code>, permet de créer des pools de threads par appel de
<code>newFixedThreadPool()</code> ou <code>newCachedThreadPool()</code> (cf supra)</li>
<li>la classe <code>ExecutorService</code> et sa superclasse <code>Executor</code>, définissent l'interface d'un
exécuteur, avec notamment les méthodes <code>submit()</code>, <code>execute()</code> (cf supra) et
<code>shutdown()</code> (gestion de la terminaison de l'exécuteur)</li>
<li>la classe <code>Future</code> fournit (immédiatement) une référence vers le résultat (à venir) d'une tâche
<code>Callable</code>soumise à l'exécuteur par <code>submit()</code>. L'appel de la méthode
<code>get()</code> permet d'obtenir le résultat effectif, en attendant s'il n'est pas encore disponible.
</li>
<li>les tâches ne renvoyant pas de résultat sont des <code>Runnable</code>, soumises à l'exécuteur par
<code>execute()</code>.</li>
</ul>
<h5 id="un-exemple">Un exemple</h5>
<pre><code>import java.util.concurrent.Future;
import java.util.concurrent.Callable;
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;
class SigmaC implements Callable&lt;Long&gt; {
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 &lt;= 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 &lt;= fin; i++) s = s + i;
System.out.println(&quot;Calcul terminé. ∑(&quot;+début+&quot;,&quot;+fin+&quot;) = &quot;+s);
}
}
public class Somme {
public static void main(String[] args) throws Exception {
ExecutorService poule = Executors.newFixedThreadPool(2);
Future&lt;Long&gt; f1 = poule.submit(new SigmaC(0L,1_000_000_000L));
Future&lt;Long&gt; f2 = poule.submit(new SigmaC(0L,4_000_000_000L));
poule.execute(new SigmaR(900_000L,1_000_000_000L));
Future&lt;Long&gt; f3 = poule.submit(new SigmaC(1,100));
Future&lt;Long&gt; f4 = poule.submit(new SigmaC(0L,3_000_000_000L));
poule.shutdown();
System.out.println(&quot;Résultat obtenu. f1 = &quot;+f1.get());
System.out.println(&quot;Résultat obtenu. f2 = &quot;+f2.get());
System.out.println(&quot;Résultat obtenu. f3 = &quot;+f3.get());
System.out.println(&quot;Résultat obtenu. f4 = &quot;+f4.get());
}
}</code></pre>
<h5 id="commentaires">Commentaires</h5>
<ul>
<li>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 (<code>Callable</code> , soumises par appel à <code>submit()</code>) rendent un
résultat <code>Future</code>, récupéré de manière bloquante par l'appel à la méthode <code>get()</code>. La
troisième (<code>Runnable</code>, soumise par appel à <code>execute()</code>) s'exécute de manière
asynchrone.</li>
<li>L'exécution voit la tâche <code>Runnable</code> terminer après la première soumise (<code>f1</code>), 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 <code>f2.get()</code> entraîne l'attente de la terminaison
de <code>f2</code>, plus longue que <code>f1</code>et la tâche <code>Runnable</code> cumulées. L'appel de
<code>f3.get()</code> retourne immédiatement, car <code>f3</code>, courte est déjà terminé. L'appel
<code>f4.get()</code> entraîne l'attente de la terminaison de <code>f4</code>.</li>
<li><code>shutdown</code> 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 <code>shutdown</code> est omis,
l'application ne peut terminer, car les threads de l'exécuteur restent en attente de nouvelles tâches.</li>
<li>L'archive contient une variante (<code>SommePlus</code>) de l'application <code>Somme</code>, qui illustre
l'utilisation de :
<ul>
<li><code>invokeAll()</code> sur une collection de tâches/actions pour soumettre une collection (ici une
liste) de <code>Callable</code>. Les résultats sont alors rendus dans une liste de
<code>Future</code>;</li>
<li><code>get()</code> sur les <code>Future</code> de cette liste, pour récupérer les résultats
effectifs</li>
</ul>
</li>
</ul>
<h3 id="pool-forkjoin-schéma-mapreduce">Pool Fork/Join (Schéma Map/Reduce)</h3>
<p>La classe <code>ForkJoinPool</code> est un exécuteur dont l'ordonnancement est adapté à une parallélisation selon
le schéma <em>fork/join</em> (voir cours, planches 43-45). Le principe (récursif) est</p>
<ul>
<li>de traiter directement (séquentiellement) un problème si sa taille est suffisamment petite</li>
<li>sinon, de diviser le problème en sous-problèmes, qui seront traités en parallèle (<code>fork</code>) et dont
les résultats seront attendus (<code>join</code>) et agrégés.</li>
</ul>
<p>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
<code>ForkJoinPool</code> comporte en outre une régulation (vol de tâches) qui permet l'adaptation de
l'exécution aux capacités de calcul disponibles.</p>
<p>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. <strong>Le schéma Fork/Join est donc idéalement et
principalement destiné aux calculs intensifs, irréguliers, en mémoire pure (sans E/S)</strong>. 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.</p>
<h5 id="classes-et-méthodes-utiles-1">Classes et méthodes utiles</h5>
<ul>
<li><code>ForkJoinPool</code>: classe définissant l'exécuteur. Une instance de <code>ForkJoinPool</code> 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).</li>
<li><code>RecursiveTask&lt;V&gt;</code> : définit une tâche soumise à l'exécuteur, fournissant un résultat</li>
<li><code>RecursiveAction</code> : définit une tâche soumise à l'exécuteur, ne fournissant pas de résultat</li>
<li><code>ForkJoinTask&lt;V&gt;</code> : superclasse de RecursiveTask<V> and RecursiveAction, définissant la
plupart des méthodes utiles, comme <code>fork()</code> et <code>join()</code>.</li>
</ul>
<h5 id="un-exemple-1">Un exemple</h5>
<p>(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.</p>
<pre><code>import java.util.concurrent.RecursiveTask;
import java.util.concurrent.ForkJoinPool;
class TraiterProblème extends RecursiveTask&lt;Integer&gt; {
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 &gt; SEUIL) {
System.out.println(&quot;Décomposition de resteAFaire : &quot; + 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(&quot;Traitement direct de resteAFaire : &quot; + 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(&quot;Résultat final = &quot; + résultat);
}
}</code></pre>
<h5 id="commentaires-1">Commentaires</h5>
<ul>
<li>la méthode abstraite <code>compute()</code> définie dans <code>RecursiveTask</code> et
<code>RecursiveAction</code> contient le code du calcul récursif proprement dit. C'est l'analogue de la
méthode <code>run()</code> pour la classe <code>Runnnable</code> ou de la méthode <code>call()</code> pour
la classe <code>Callable</code>.</li>
<li><code>SEUIL</code> 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.</li>
<li>le <code>ForkJoinPool</code> 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
<code>ForkJoinPool</code> 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 <code>ForkJoinPool.commonPool()</code>. L'archive contient
une variante (<code>FJGPlus</code>) de l'application <code>FJG</code>, qui utilise cette facilité.</li>
<li>l'appel <code>fjp.invoke(monProblème);</code> permet de soumettre la tâche racine au pool.</li>
</ul>
<h5 id="quelques-écueils">Quelques écueils</h5>
<ul>
<li>
<p>l'implémentation actuelle de <code>ForkJoinPool</code> est d'autant moins efficace que les tâches sont
nombreuses. Ainsi, l'implémentation suivante de la branche <code>if</code> de la méthode
<code>compute</code> précédente aurait été sensiblement plus efficace (mais moins naturelle) :</p>
<pre><code> if(this.resteAFaire &gt; SEUIL) {
System.out.println(&quot;Décomposition de resteAFaire : &quot; + 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;
}</code></pre>
</li>
<li>
<p>il ne faut pas oublier que <code>join()</code> est bloquant. Ainsi l'échange des appels à
<code>join()</code> et <code>compute()</code> dans la variante précédente aurait pour effet d'aboutir à
un programme séquentiel...</p>
</li>
</ul>
<h2 id="exercices">Exercices</h2>
<p>Vous aurez vraisemblablement besoin pour cette partie de deux méthodes de classe de la classe <code>System</code>
: <code>System.nanoTime()</code> et <code>System.currentTimeMillis()</code> 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.</p>
<p>L'archive fournie propose différents exercices.<br />
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.<br />
Chaque exercice comporte une méthode main permettant de lancer et comparer les différentes versions. Des
commentaires <code>// ********* A compléter</code> ou <code>// ********* A corriger</code> signalent les (seuls)
endroits du code où vous devez intervenir pour implanter les versions parallèles du calcul séquentiel fourni.
</p>
<p>Les exercices utilisent des tableaux d'entiers stockés sur disque.<br />
L'archive fournie comporte une application GCVT.java qui propose une classe <code>TableauxDisque</code>
permettant de générer, charger en mémoire, sauvegarder ou comparer de tels tableaux.<br />
La méthode <code>main</code>de l'application GCVT.java permet en outre d'appeler les méthodes de la classe
<code>TableauxDisque</code> depuis la console.<br />
<em>Cette application pourra en particulier être utilisée pour générer les jeux de données utiles aux
tests.</em> 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, <em>sans oublier de supprimer les fichiers créés
une fois le TP passé</em>, sans quoi vous risquez d'épuiser votre quota d'espace disque :)
</p>
<p>Les exercices peuvent être traités dans l'ordre suivant :</p>
<ul>
<li>Calcul du maximum d'un tableau (répertoire <code>max</code>). 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é.
<ul>
<li>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 ?</li>
<li>Notez que le calcul étant très simple, il est important pour évaluer les performances de cet
exercice de travailler avec un grand tableau.</li>
<li>Comparer les deux versions (pool fixe et Fork/join) avec la version séquentielle. Les mesures
confirment-elles vos a priori ? Commentez.</li>
</ul>
</li>
<li>Tri d'un tableau selon le schéma tri-fusion (répertoire <code>tri fusion</code>). 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).
<ul>
<li>Paralléliser l'algorithme récursif proposé en utilisant les deux schémas (pool fixe et Fork/Join)
</li>
<li>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.</li>
</ul>
</li>
<li><strong>Pour aller plus loin</strong> (non demandé pour ce TP), l'application de comptage de mots dans un
répertoire (répertoire <code>comptage des mots d'un répertoire</code>) réalise la commande
<code>find repertoire -exec grep mot {}\;</code> Elle permet d'illustrer la parallélisation d'un problème
irrégulier.
<ul>
<li>Paralléliser l'algorithme récursif proposé en utilisant le schéma fork/join</li>
<li>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.</li>
</ul>
</li>
</ul>
<h2 id="tester-les-performances-dapplications-concurrentes-en-java-quelques-remarques-pratiques">Tester les
performances d'applications concurrentes en Java : quelques remarques pratiques</h2>
<ul>
<li>sources de perturbation : cache, compilateur à la volée, ramasse miettes et optimiseur, charge de
l'environnement (matériel, réseau) -&gt; répéter les mesures et retenir la meilleure</li>
<li>tester sur des volumes de données significatifs</li>
<li>connaître le nombre de processeurs réels disponibles</li>
<li>éviter les optimisations sauvages
<ul>
<li>avoir des tâches suffisamment complexes<br />
</li>
<li>avoir un jeu de données varié (non constant en valeur et dans le temps)</li>
</ul>
</li>
<li>arrêter la décomposition en sous tâches à un seuil raisonnable</li>
</ul>
</body>
</html>

398
TP5/README.md Normal file
View file

@ -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<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 `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<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<V> 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 `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

View file

@ -0,0 +1,163 @@
/* v0.0 9/10/16 (PM).
* Comptage <répertoire> <mot>
* Compte le nombre d'occurrences de <mot> dans l'ensemble des documents
* appartenant à la sous arborescence dont <répertoire> 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<Long> {
private Path racine;
private String mot;
CompterMots(Path p, String m) {
racine = p;
mot = m;
}
@Override
protected Long compute() {
long cpt = 0L;
List<CompterMots> filles = new LinkedList<>();
DirectoryStream<Path> 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<Path> 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 <répertoire> <mot> <nb essais>");
}
}
if ((nbEssais < 1) || (!Files.isDirectory(Paths.get(chemin), LinkOption.NOFOLLOW_LINKS)
&& !Files.isRegularFile(Paths.get(chemin), LinkOption.NOFOLLOW_LINKS)))
throw new IllegalArgumentException("Usage : Comptage <répertoire> <mot> <nb essais>");
// 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("--------------------");
}
}

BIN
TP5/données/t1000 Normal file

Binary file not shown.

View file

@ -0,0 +1,47 @@
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 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);
}
}

View file

@ -0,0 +1,57 @@
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();
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();
}
}

View file

@ -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<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());
}
}

View file

@ -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<Long> {
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<Future<Long>> results = executor.invokeAll(asList(
new Sum(0, 10), new Sum(100, 1_000), new Sum(10_000, 1_000_000)
));
executor.shutdown();
for (Future<Long> result : results) {
System.out.println(result.get());
}
}
}

176
TP5/max/GCVT.java Normal file
View file

@ -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<taille; i++) {
tableau[i]=(Math.abs(rdm.nextInt()%max));
}
ObjectOutputStream flot = new ObjectOutputStream(new FileOutputStream(chemin));
flot.writeObject (tableau);
flot.close();
return true;
}
}
static int [] charger(String chemin) throws IOException, FileNotFoundException {
/* retourne le tableau contenu dans le fichier dont le chemin est fourni en argument */
int [] tableau;
if (!Files.isRegularFile(Paths.get(chemin), LinkOption.NOFOLLOW_LINKS)) {
System.err.println("Charger : erreur chemin/fichier ("+chemin+")");
return null;
} else {
ObjectInputStream flot = new ObjectInputStream(new FileInputStream(chemin));
try { tableau=(int[])flot.readObject(); }
catch (ClassNotFoundException cnfe) {
System.err.println("charger : "+cnfe);
tableau=null;
}
flot.close();
return tableau;
}
}
static boolean comparer(String chemin, String chemin2, int début)
throws IOException, FileNotFoundException {
int [] t1;
int [] t2;
int i = 0;
boolean résultat;
if (!Files.isRegularFile(Paths.get(chemin), LinkOption.NOFOLLOW_LINKS) ||
!Files.isRegularFile(Paths.get(chemin2), LinkOption.NOFOLLOW_LINKS)) {
System.err.println("Comparer : erreur chemin/fichier ("+chemin+"|"+chemin2+")");
return false;
} else {
t1 = charger(chemin);
t2 = charger(chemin2);
résultat = (t1.length==t2.length);
while (résultat && (i<t1.length)) {
résultat = (t1[i] == t2[i]);
i++;
}
return résultat;
}
}
static void sauver(String chemin, int [] tableau) throws IOException, FileNotFoundException {
/* enregistre le tableau en argument dans le fichier dont le chemin est fourni en argument */
ObjectOutputStream flot = new ObjectOutputStream(new FileOutputStream(chemin));
flot.writeObject (tableau);
flot.close();
}
static boolean visualiser(String chemin, int début, int fin)
throws IOException, FileNotFoundException {
/* visualise les élements début à fin
du tableau contenu dans le fichier dont le chemin est fourni en argument */
Random rdm = new Random();
int [] tableau;
if ((début < 0)||(fin < début) ||
!Files.isRegularFile(Paths.get(chemin), LinkOption.NOFOLLOW_LINKS)) {
System.err.println("Visualiser : paramètres inattendus");
return false;
} else {
ObjectInputStream flot = new ObjectInputStream(new FileInputStream(chemin));
try { tableau=(int[])flot.readObject(); }
catch (ClassNotFoundException cnfe) {
System.err.println("visualiser : "+cnfe);
tableau=null;
}
flot.close();
System.out.println("--------------------");
for (int i=début; i<=Math.min((tableau.length-1),fin); i++) {
System.out.println(i+":"+tableau[i]);
}
System.out.println("--------------------");
return true;
}
}
}
public class GCVT {
public static void main(String[] args) throws IOException, FileNotFoundException {
Random rdm = new Random();
int [] tableau;
int taille=0;
int max=0;
String chemin="";
String chemin2="";
String option="-X";
if (args.length == 4) { //analyse des paramètres
option = args[0];
chemin = args[1];
try {
if (option.equals("-c")) {
chemin2 = args[2];
} else {
taille = Integer.parseInt (args[2]);
}
max= Integer.parseInt (args[3]);
}
catch (NumberFormatException nfx) {
option="-X";
}
}
if ((taille < 0) || (max<0)) option="-X";
switch (option) {
case "-g":
TableauxDisque.générer(chemin, taille, max);
break;
case "-c":
System.out.println("Identiques : " + TableauxDisque.comparer(chemin, chemin2, max));
break;
case "-v":
TableauxDisque.visualiser(chemin, taille, max);
break;
case "-t": //juste un test
TableauxDisque.générer(chemin, taille, max);
TableauxDisque.visualiser(chemin, 0, 20);
tableau=TableauxDisque.charger(chemin);
System.out.println("Chargé : ");
for (int i=0; i<tableau.length; i++) {
System.out.println(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 <fichier> <taille> <valeur max> (pour générer un tableau"
+" de <taille> entiers inférieurs à <valeur max>)\n"
+ " ou GCVT -c <fichier1> <fichier2> <début> (pour comparer le contenu"
+ " de 2 fichiers à partir de la position <début>)\n"
+ " ou GCVT -v <fichier> <début> <fin> (pour visualiser le contenu d'un"
+ " fichier entre les positions <début> et <fin>)\n"
+ "Note : <fichier> est un chemin, qui peut être relatif. Exemple ../data/t1000\n");
}
}
}

193
TP5/max/MaxTab.java Normal file
View file

@ -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<Integer> {
// 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<Integer> {
// 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<Future<Integer>> résultats = new LinkedList<Future<Integer>>();
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) 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 <fichier> <nb essais>"
+ " <nb tâches (pool)> <taille tronçon (FJ)>" + " <nb ouvriers du pool (pool)>\n"
+ " * <nb tâches (pool)> = nb de fragments à traiter \n"
+ " * <taille tronçon (FJ)> = 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 <fichier> <nb essais>" + " <nb tâches (pool)> <taille tronçon (FJ)>"
+ " <nb ouvriers du pool (pool)>\n" + " * <nb tâches (pool)> = nb de fragments à traiter \n"
+ " * <taille tronçon (FJ)> = 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("--------------------");
}
}

BIN
TP5/max/array Normal file

Binary file not shown.

21
TP5/reponses.md Normal file
View file

@ -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 lalgorithme 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.

176
TP5/tri fusion/GCVT.java Normal file
View file

@ -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<taille; i++) {
tableau[i]=(Math.abs(rdm.nextInt()%max));
}
ObjectOutputStream flot = new ObjectOutputStream(new FileOutputStream(chemin));
flot.writeObject (tableau);
flot.close();
return true;
}
}
static int [] charger(String chemin) throws IOException, FileNotFoundException {
/* retourne le tableau contenu dans le fichier dont le chemin est fourni en argument */
int [] tableau;
if (!Files.isRegularFile(Paths.get(chemin), LinkOption.NOFOLLOW_LINKS)) {
System.err.println("Charger : erreur chemin/fichier ("+chemin+")");
return null;
} else {
ObjectInputStream flot = new ObjectInputStream(new FileInputStream(chemin));
try { tableau=(int[])flot.readObject(); }
catch (ClassNotFoundException cnfe) {
System.err.println("charger : "+cnfe);
tableau=null;
}
flot.close();
return tableau;
}
}
static boolean comparer(String chemin, String chemin2, int début)
throws IOException, FileNotFoundException {
int [] t1;
int [] t2;
int i = 0;
boolean résultat;
if (!Files.isRegularFile(Paths.get(chemin), LinkOption.NOFOLLOW_LINKS) ||
!Files.isRegularFile(Paths.get(chemin2), LinkOption.NOFOLLOW_LINKS)) {
System.err.println("Comparer : erreur chemin/fichier ("+chemin+"|"+chemin2+")");
return false;
} else {
t1 = charger(chemin);
t2 = charger(chemin2);
résultat = (t1.length==t2.length);
while (résultat && (i<t1.length)) {
résultat = (t1[i] == t2[i]);
i++;
}
return résultat;
}
}
static void sauver(String chemin, int [] tableau) throws IOException, FileNotFoundException {
/* enregistre le tableau en argument dans le fichier dont le chemin est fourni en argument */
ObjectOutputStream flot = new ObjectOutputStream(new FileOutputStream(chemin));
flot.writeObject (tableau);
flot.close();
}
static boolean visualiser(String chemin, int début, int fin)
throws IOException, FileNotFoundException {
/* visualise les élements début à fin
du tableau contenu dans le fichier dont le chemin est fourni en argument */
Random rdm = new Random();
int [] tableau;
if ((début < 0)||(fin < début) ||
!Files.isRegularFile(Paths.get(chemin), LinkOption.NOFOLLOW_LINKS)) {
System.err.println("Visualiser : paramètres inattendus");
return false;
} else {
ObjectInputStream flot = new ObjectInputStream(new FileInputStream(chemin));
try { tableau=(int[])flot.readObject(); }
catch (ClassNotFoundException cnfe) {
System.err.println("visualiser : "+cnfe);
tableau=null;
}
flot.close();
System.out.println("--------------------");
for (int i=début; i<=Math.min((tableau.length-1),fin); i++) {
System.out.println(i+":"+tableau[i]);
}
System.out.println("--------------------");
return true;
}
}
}
public class GCVT {
public static void main(String[] args) throws IOException, FileNotFoundException {
Random rdm = new Random();
int [] tableau;
int taille=0;
int max=0;
String chemin="";
String chemin2="";
String option="-X";
if (args.length == 4) { //analyse des paramètres
option = args[0];
chemin = args[1];
try {
if (option.equals("-c")) {
chemin2 = args[2];
} else {
taille = Integer.parseInt (args[2]);
}
max= Integer.parseInt (args[3]);
}
catch (NumberFormatException nfx) {
option="-X";
}
}
if ((taille < 0) || (max<0)) option="-X";
switch (option) {
case "-g":
TableauxDisque.générer(chemin, taille, max);
break;
case "-c":
System.out.println("Identiques : " + TableauxDisque.comparer(chemin, chemin2, max));
break;
case "-v":
TableauxDisque.visualiser(chemin, taille, max);
break;
case "-t": //juste un test
TableauxDisque.générer(chemin, taille, max);
TableauxDisque.visualiser(chemin, 0, 20);
tableau=TableauxDisque.charger(chemin);
System.out.println("Chargé : ");
for (int i=0; i<tableau.length; i++) {
System.out.println(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 <fichier> <taille> <valeur max> (pour générer un tableau"
+" de <taille> entiers inférieurs à <valeur max>)\n"
+ " ou GCVT -c <fichier1> <fichier2> <début> (pour comparer le contenu"
+ " de 2 fichiers à partir de la position <début>)\n"
+ " ou GCVT -v <fichier> <début> <fin> (pour visualiser le contenu d'un"
+ " fichier entre les positions <début> et <fin>)\n"
+ "Note : <fichier> est un chemin, qui peut être relatif. Exemple ../data/t1000\n");
}
}
}

242
TP5/tri fusion/TF.java Normal file
View file

@ -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<int[]> {
// 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<int[]> {
// 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<Future<int[]>> résultats = new ArrayList<Future<int[]>>(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) 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 <fichier> <nb essais> " + " <nb tâches (pool)> <seuil>"
+ " <nb ouvriers du pool (pool)>\n" + " * <nb tâches (pool)> = nb de fragments à traiter \n"
+ " * <seuil> = 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 <fichier> <nb essais> " + " <nb tâches (pool)> <seuil>"
+ " <nb ouvriers du pool (pool)>\n" + " * <nb tâches (pool)> = nb de fragments à traiter \n"
+ " * <seuil> = 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("--------------------");
}
}

BIN
TP5/tri fusion/array Normal file

Binary file not shown.

BIN
TP5/tri fusion/arraytriéFJ Normal file

Binary file not shown.

Binary file not shown.

BIN
TP5/tri fusion/arraytriéPF Normal file

Binary file not shown.

6
TP6/Makefile Normal file
View file

@ -0,0 +1,6 @@
all :
gprbuild build.gpr
clean :
gprclean build.gpr

76
TP6/README.html Normal file
View file

@ -0,0 +1,76 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta http-equiv="Content-Style-Type" content="text/css" />
<meta name="generator" content="pandoc" />
<title></title>
<style type="text/css">code{white-space: pre;}</style>
</head>
<body>
<h1 id="tp-synchronisation-en-ada-lecteursrédacteurs">TP Synchronisation en Ada : lecteurs/rédacteurs</h1>
<h2 id="objectif">Objectif</h2>
<p>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 :</p>
<ul>
<li>priorité aux rédacteurs ou aux lecteurs ;</li>
<li>FIFO ;</li>
<li>équitable (absence de famine que ce soient des lecteurs ou des rédacteurs).</li>
</ul>
<h2 id="les-fournitures-et-paquetages">Les fournitures et paquetages</h2>
<p><code>mkstrategie</code> : script shell permettant de définir, choisir et compiler une stratégie</p>
<p><code>LR</code> : vide, requis par l'implantation d'Ada utilisée ici (gnat)<br />
<code>LR.Affic</code> : tout ce qui concerne l'affichage<br />
<code>LR.Main</code> : programme principal<br />
<code>LR.Simu</code> : simulateur temporel<br />
<code>LR.Synchro</code> : paquetage de redirection vers la stratégie choisie<br />
<code>LR.Synchro.Exclusion</code> : implantation LR par exclusion mutuelle (approche par conditions d'acceptation)<br />
<code>LR.Synchro.Exclusion2</code> : implantation alternative de LR par exclusion mutuelle (approche par automates)<br />
<code>LR.Synchro.Basique</code> : 1er exercice, à compléter<br />
<code>LR.Tasks</code> : les tâches clientes (lecteurs et rédacteurs)</p>
<p>Les paquetages à consulter sont principalement les paquetages <code>LR.Synchro.*</code></p>
<p>Au besoin, il est possible de consulter <code>LR.Tasks</code> 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.</p>
<p>Par curiosité, on peut aussi consulter <code>LR.Main</code> (qui lance les tâches) et les interfaces de <code>LR.Simu</code> et <code>LR.Affic</code> 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.</p>
<h2 id="principe-de-la-synchronisation">Principe de la synchronisation</h2>
<p>Comme présenté en cours, deux approches sont possibles :</p>
<p>1 - définir une tâche de synchronisation (par exemple <code>LectRedTask</code> dans <code>LR.Synchro.Exclusion</code>) qui possède des entrées ouvertes ou pas selon son état interne. La tâche de synchronisation est alors conçue comme un <em>automate</em>. L'interface (= les entrées) et leur implantation peuvent varier selon la stratégie implantée. Les procédures <code>Demander_*</code> et <code>Terminer_*</code> permettent de présenter une interface uniforme pour les tâches définies dans <code>LR.Tasks</code>.</p>
<p>2 - définir une tâche fournissant un <em>service</em>, 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.</p>
<h2 id="à-faire">À Faire</h2>
<ol style="list-style-type: decimal">
<li><p>Écrire dans <code>lr-synchro-basique.adb</code> 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.</p></li>
<li><p>Définir une tâche serveur réalisant le même service (approche 2).</p></li>
<li><p>Modifier les versions précédentes pour implanter une stratégie de type priorité aux rédacteurs.</p></li>
<li><p>Modifier les versions précédentes pour implanter une stratégie FIFO.</p></li>
<li><p>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.</p></li>
</ol>
<h2 id="définition-des-stratégies">Définition des stratégies</h2>
<p>L'absence d'introspection en Ada, qui aurait par exemple permis de paramétrer <code>LR.Synchro</code> avec le nom des paquetages/stratégies disponibles, alourdit un peu le travail d'édition des différents composants.</p>
<p>Le script shell <code>./mkstrategie</code> vise à faciliter ce travail d'édition :</p>
<ul>
<li>appelé sans paramètres (<code>./mkstrategie</code>), il permet de recompiler la dernière stratégie &quot;installée&quot; ;</li>
<li>avec l'option -i (<code>./mkstrategie -i</code>), 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.</li>
</ul>
<p>Pour définir une stratégie (<code>Xxxx</code>), il suffit de travailler sur le fichier <code>lr-synchro-xxxx.adb</code>, correspondant à son implantation. Seul le corps de la tâche <code>LectRedTask</code>, ainsi que la fonction <code>Nom_Strategie</code> doivent être modifiés. En particulier, les procédures <code>Demander_Lecture</code>, <code>Demander_Ecriture</code>, <code>Terminer_Lecture</code>, <code>Terminer_Ecriture</code> qui appellent les entrées correspondantes de la tâche <code>LectRedTask</code> n'ont pas à être modifiées (sauf, bien sûr, si l'on souhaite modifier l'interface de la tâche <code>LectRedTask</code>). 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 <code>LectRedTask</code>.</p>
<p>Il est par ailleurs possible de réaliser les opérations du script <code>./mkstrategie</code> à la main. Le détail des opérations est donné en fin de document.</p>
<h2 id="pour-exécuter">Pour exécuter</h2>
<pre><code>./lr-main 5 4 (nb lecteurs, nb rédacteurs)</code></pre>
<p><em>Note :</em> le bouton d'aide de la fenêtre affichée par l'application en présente les fonctionnalités.</p>
<h1 id="annexe">Annexe</h1>
<h2 id="rappel-tâches-ada">Rappel tâches Ada</h2>
<ul>
<li>Pour une entrée de rendez-vous <code>Rdv</code>, <code>Rdv'count</code> est le nombre de tâches clientes en attente de l'acceptation du rendez-vous.</li>
</ul>
<h2 id="ajouter-une-nouvelle-stratégie-sans-utiliser-le-script-mkstrategie">Ajouter une nouvelle stratégie sans utiliser le script <code>mkstrategie</code></h2>
<p>Soit la stratégie <code>Toto</code> que l'on souhaite implanter :</p>
<ul>
<li>dupliquer <code>lr-synchro.ads</code> dans <code>lr-synchro-toto.ads</code> ;</li>
<li>éditer <code>lr-synchro-toto.ads</code> pour nommer le paquetage <code>LR.Synchro.Toto</code> ; toutes les stratégies ont ainsi la même interface, mais des noms différents.</li>
<li>dans <code>lr-synchro.adb</code>, remplacer <code>LR.Synchro.Exclusion</code> (en supposant que <code>Exclusion</code> est la dernière stratégie utilisée) par <code>LR.Synchro.Toto</code> (deux emplacements marqués par <code>XXXX</code>) ; cette manipulation (pas vraiment élégante...) vise à pallier l'absence d'introspection en Ada, qui aurait ici permis de paramétrer <code>LR.Synchro</code> avec le nom des paquetages/stratégies disponibles.</li>
<li>écrire l'implantation du paquetage <code>LR.Synchro.Toto</code> dans le fichier <code>lr-synchro-toto.adb</code> en s'inspirant d'une stratégie déjà existante.</li>
</ul>
<h2 id="pour-compiler">Pour compiler</h2>
<ul>
<li>assurez vous d'utiliser le compilateur Ada situé dans <code>usr/bin</code> (au besoin : <code>export PATH=/usr/bin:$PATH</code>)</li>
<li><code>make</code> ou <code>gprbuild build.gpr</code> ou <code>./mkstrategie</code></li>
</ul>
</body>
</html>

137
TP6/README.md Normal file
View file

@ -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`

4
TP6/src/build.gpr Normal file
View file

@ -0,0 +1,4 @@
With "gtkada";
project Build is
for Main use ("lr-main");
end Build;

358
TP6/src/lectred.glade Normal file
View file

@ -0,0 +1,358 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.22.1 -->
<interface>
<requires lib="gtk+" version="3.20"/>
<object class="GtkAdjustment" id="adjustment1">
<property name="lower">1</property>
<property name="upper">100</property>
<property name="value">1</property>
<property name="step_increment">1</property>
<property name="page_increment">10</property>
</object>
<object class="GtkWindow" id="LectRed">
<property name="can_focus">False</property>
<property name="title" translatable="yes">Lecteurs/Rédacteurs</property>
<property name="default_width">350</property>
<property name="default_height">400</property>
<signal name="destroy" handler="aff_quitter" swapped="no"/>
<child>
<placeholder/>
</child>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="spacing">40</property>
<property name="homogeneous">True</property>
<child>
<object class="GtkButton" id="bouton_quitter">
<property name="label" translatable="yes">Quitter</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<signal name="clicked" handler="aff_quitter" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="bouton_pause">
<property name="label" translatable="yes">Pause</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<signal name="clicked" handler="aff_pause_or_run" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkButton" id="bouton_aide">
<property name="label" translatable="yes">Aide</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<signal name="clicked" handler="aff_aide_afficher" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkSeparator">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="padding">5</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label_strategie">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Stratégie</property>
<property name="justify">center</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkSeparator">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="padding">5</property>
<property name="position">3</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">REDACTEURS</property>
<property name="justify">center</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">4</property>
</packing>
</child>
<child>
<object class="GtkBox" id="drawingarea_all">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkDrawingArea" id="drawingarea_redacteur">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="events">GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_STRUCTURE_MASK</property>
<signal name="button-release-event" handler="aff_click_event_red" swapped="no"/>
<signal name="configure-event" handler="aff_compute_placement" swapped="no"/>
<signal name="map-event" handler="aff_compute_placement" swapped="no"/>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkAlignment">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="xscale">0.5</property>
<child>
<object class="GtkFrame">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label_xalign">0</property>
<property name="label_yalign">0</property>
<child>
<object class="GtkDrawingArea" id="drawingarea_inside">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="events">GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_STRUCTURE_MASK</property>
<signal name="button-release-event" handler="aff_click_event_inside" swapped="no"/>
</object>
</child>
<child type="label_item">
<placeholder/>
</child>
</object>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkDrawingArea" id="drawingarea_lecteur">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="events">GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_STRUCTURE_MASK</property>
<signal name="button-release-event" handler="aff_click_event_lect" swapped="no"/>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">5</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">LECTEURS</property>
<property name="justify">center</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">6</property>
</packing>
</child>
<child>
<object class="GtkSeparator">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="padding">5</property>
<property name="position">7</property>
</packing>
</child>
<child>
<object class="GtkEventBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Vitesse du temps: </property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkScale" id="timespeed">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="adjustment">adjustment1</property>
<property name="fill_level">100</property>
<property name="round_digits">1</property>
<property name="digits">0</property>
<property name="value_pos">bottom</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">9</property>
</packing>
</child>
</object>
</child>
</object>
<object class="GtkDialog" id="dialog_aide">
<property name="can_focus">False</property>
<property name="title" translatable="yes">Aide</property>
<property name="default_width">480</property>
<property name="default_height">300</property>
<property name="type_hint">dialog</property>
<child>
<placeholder/>
</child>
<child internal-child="vbox">
<object class="GtkBox">
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<property name="spacing">2</property>
<child internal-child="action_area">
<object class="GtkButtonBox">
<property name="can_focus">False</property>
<property name="layout_style">end</property>
<child>
<object class="GtkButton">
<property name="label" translatable="yes">Fermer</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<signal name="clicked" handler="aff_aide_fermer" swapped="no"/>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkScrolledWindow">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="shadow_type">in</property>
<child>
<object class="GtkViewport">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">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.</property>
<property name="wrap">True</property>
</object>
</child>
</object>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
</object>
</interface>

352
TP6/src/lr-affic.adb Normal file
View file

@ -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;

14
TP6/src/lr-affic.ads Normal file
View file

@ -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;

61
TP6/src/lr-main.adb Normal file
View file

@ -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;

83
TP6/src/lr-simu.adb Normal file
View file

@ -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;

40
TP6/src/lr-simu.ads Normal file
View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

17
TP6/src/lr-synchro.adb Normal file
View file

@ -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;

10
TP6/src/lr-synchro.ads Normal file
View file

@ -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;

87
TP6/src/lr-tasks.adb Normal file
View file

@ -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;

21
TP6/src/lr-tasks.ads Normal file
View file

@ -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;

2
TP6/src/lr.ads Normal file
View file

@ -0,0 +1,2 @@
package LR is
end LR;

131
TP6/src/mkstrategie Normal file
View file

@ -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-<nom_stratégie>.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

40
TP7/README.html Normal file
View file

@ -0,0 +1,40 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta http-equiv="Content-Style-Type" content="text/css" />
<meta name="generator" content="pandoc" />
<title></title>
<style type="text/css">code{white-space: pre;}</style>
</head>
<body>
<h1 id="transactions-et-mémoire-transactionnelle">Transactions et mémoire transactionnelle</h1>
<p>Cette archive contient</p>
<ul>
<li>une classe <code>Simulator</code> qui correspond au point d'entrée du simulateur de mémoire transactionnelle. Elle contient la méthode main.</li>
<li>2 paquetages <code>tm</code> et <code>simulation</code>, qui contiennent respectivement les classes des éléments de mémoire transactionnelle et les classes des différentes simulations.</li>
<li>un Makefile pour compiler le code source. <strong>Cette approche est recommandée par rapport à l'utilisation d'un IDE comme Eclipse</strong>.</li>
<li>un dossier <code>scenarios</code> qui contient des exemples de scénarios d'accès à une mémoire transactionnelle.
<ul>
<li><code>scenario0</code> est destiné (uniquement) au simulateur en mode interpréteur (pas à pas)</li>
<li><code>scenario1</code>, <code>scenario2</code> et <code>scenario3</code> sont destinés (uniquement) au simulateur en mode &quot;simulation complète&quot;</li>
</ul></li>
</ul>
<h2 id="utilisation-du-simulateur-en-mode-interpréteur-pas-à-pas">Utilisation du simulateur en mode &quot;Interpréteur (pas à pas)&quot;</h2>
<ul>
<li><em>Compilation</em> : <code>make</code></li>
<li><em>Exécution</em> : <code>make shell</code></li>
<li><em>Aide (dans le simulateur)</em> : <code>help</code></li>
<li><em>Lancement d'un scénario (dans le simulateur)</em> : <code>run &lt;nom scénario&gt;</code></li>
</ul>
<p>Le fichier <code>scenario0</code> correspond au premier scénario de la section 3 du sujet, pour le protocole PP.<br />
<em>Note</em> : <code>&lt;nom scénario&gt;</code> correspond au <em>chemin d'accès</em> au fichier.</p>
<h2 id="utilisation-du-simulateur-en-mode-simulation-complète">Utilisation du simulateur en mode &quot;Simulation complète&quot;</h2>
<ul>
<li><em>Compilation</em> : <code>make</code></li>
<li><em>Exécution</em> : <code>make simu SCEN=&lt;nom_scénario&gt;</code></li>
</ul>
<p>Les fichiers <code>scenario1</code>, <code>scenario2</code> et <code>scenario3</code> correspondent aux scénarios proposés dans la section 4 du sujet.<br />
<em>Note</em> : <code>&lt;nom scénario&gt;</code> correspond au <em>chemin d'accès</em> au fichier.</p>
</body>
</html>

30
TP7/README.md Normal file
View file

@ -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 <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.

BIN
TP7/TpTransactions-1.pdf Normal file

Binary file not shown.

231
TP7/reponses.md Normal file
View file

@ -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

16
TP7/src/Makefile Normal file
View file

@ -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

42
TP7/src/Simulator.java Normal file
View file

@ -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();
}
}

View file

@ -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);
}
}

Some files were not shown because too many files have changed in this diff Show more