Conception et programmation parallèle

Transcription

Conception et programmation parallèle
Université Paul Sabatier
31062 Toulouse Cedex 09
Corrigé des TD (2ème partie)
2/10
Synchronisation des joueurs à l’aide d’une seule méthode « jouer »
Master 1 Informatique – TC1
Année 2008-2009
Conception et programmation parallèle
Éléments de correction des travaux dirigés (3ème partie : séance 9)
TD n°9 – Synchronisation de threads Java
Exercice
On reprend le problème du TD6 qui consiste à simuler le déroulement d’une partie de ping-pong. On
désire à présent implanter la solution « théorique » trouvée précédemment à l’aide de threads Java
qui se synchronisent via des verrous réentrants.
Un instance d’une classe Java jouera le rôle du « moniteur de Hoare » qui permettra de synchroniser
les accès des joueurs à la table de jeu. Un joueur sera implanté par un thread Java utilisant ce
« moniteur » pour accéder à la table partagée selon le schéma demandé.
Solutions possibles
Différentes solutions peuvent exister :
•
la classe Joueur hérite de la classe java Thread ;
•
la classe Joueur implémente l’interface Runnable ;
•
la classe jouant le rôle du « moniteur de Hoare » qui permet de synchroniser les accès des
joueurs à la table propose deux méthodes (accéder à la table et la libérer) ou une seule (jouer).
Et les solutions proposées ci-dessous mettent en avant l’un de ces points. Il suffit d’adopter celle qui
vous convient le mieux.
La dernière solution proposée est équivalente en termes de synchronisation mais propose une
approche plus « orientée objet » qui correspond mieux à ce que l’on attend de vous en BE et en
conception.
Master 1 Informatique – Conception et programmation parallèle
Classe gérant la synchronisation des joueurs pour accéder à tour de rôle à la ressource partagée qu’est
la table
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class Table {
// Verrou réentrant pour assurer l'E.M. des opérations
private final ReentrantLock verrou;
// Bloquer les joueurs Ping et Pong dans 2 files
private final Condition [] peutJouer;
// Variables d'état : qui vient de jouer
private int vientDeJouer = 0;
// Arbitraire, Pong vient de jouer
// Constructeur de la classe
public Table () {
verrou = new ReentrantLock();
peutJouer = new Condition[2];
peutJouer[0] = verrou.newCondition();
peutJouer[1] = verrou.newCondition();
}
/* Solution étudiée avec les moniteurs de Hoare */
public void jouer (int typeJoueur, int num) throws InterruptedException {
verrou.lock();
try {
// Se bloquer si on ne peut pas jouer
while (vientDeJouer == typeJoueur) {
peutJouer[vientDeJouer].await();
}
// Acces table
vientDeJouer = typeJoueur;
System.out.println("Joueur " + typeJoueur + ": je joue pour la
" + num + " fois");
// On a fini de jouer, c'est à l'autre, on le réveille
peutJouer[(vientDeJouer + 1)%2].signal();
}
finally {
verrou.unlock();
}
}
}
Master 1 Informatique – Conception et programmation parallèle
Corrigé des TD (2ème partie)
3/10
Corrigé des TD (2ème partie)
// Attendre la fin d’exécution des threads
try {
joueurPing.join();
} catch (InterruptedException e1) {
e1.printStackTrace();
}
try {
joueurPong.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Fin application ");
Classe étendant la classe Thread de manière à construire un joueur qui joue Ping ou Pong (selon la
manière dont une instance de cette classe sera créée)
public class Joueur extends Thread {
private int monType;
private Table maTable;
4/10
// Accès à la table via cette classe
public Joueur (Table uneTable, int typeJoueur) {
monType = typeJoueur;
maTable = uneTable;
System.out.println("Joueur " + monType + " defini");
}
}
}
// Surcharge de run => comportement du joueur
public void run() {
int i;
for (i = 0; i < 10; i++) {
try {
maTable.jouer(monType, i);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (monType == 0)
try {
sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
} // On le ralentit pour désynchroniser un peu les 2 joueurs
}
}
}
Synchronisation des joueurs à l’aide de deux méthodes « accéder à la ressource
partagée » et « libérer cette ressource »
Classe gérant la synchronisation des joueurs pour accéder à tour de rôle à la ressource partagée qu’est
la table
La solution précédente est insuffisante si on scinde la méthode « jouer » en deux car un thread peut
accéder à la table alors que le précédent n’a pas fini de jouer. La condition d’accès à la table doit donc
être légèrement modifiée : on accède à la table si on ne vient pas de jouer et si elle est libre.
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class Table {
// Verrou réentrant pour assurer l'E.M. des opérations
private final ReentrantLock verrou;
// Bloquer les joueurs Ping et Pong dans 2 files
private final Condition [] peutJouer;
Classe mettant en place l’application à réaliser
// Variables d'état : qui vient de jouer + table occupée ?
private int vientDeJouer = 0;
// Arbitraire, Pong vient de jouer
private boolean occupee = false; // Ressource occupée par un joueur
public class PingPong_VThread {
public static void main(String[] args) {
// Créer le « moniteur » gérant la synchronisation de l'application
// Il est le même pour tous les joueurs (sinon pas de synchro)
Table laTable = new Table();
// Constructeur de la classe
public Table () {
verrou = new ReentrantLock();
peutJouer = new Condition[2];
peutJouer[0] = verrou.newCondition();
peutJouer[1] = verrou.newCondition();
}
// Créer les deux joueurs
Joueur joueurPing = new Joueur(laTable, 0);
Joueur joueurPong = new Joueur(laTable, 1);
// Lancer leur exécution joueurs
joueurPing.start();
joueurPong.start();
System.out.println("Fin lancement application ");
Master 1 Informatique – Conception et programmation parallèle
public void demanderAccesTable(int typeJoueur) throws InterruptedException
{
verrou.lock();
try {
// On ne peut pas jouer si :
// on vient de jouer ou il y a déjà quelqu'un qui joue
Master 1 Informatique – Conception et programmation parallèle
Corrigé des TD (2ème partie)
5/10
while ((typeJoueur == vientDeJouer) || occupee) {
peutJouer[typeJoueur].await();
}
// Acces table
occupee = true;
vientDeJouer = typeJoueur;
Corrigé des TD (2ème partie)
Classe mettant en place l’application à réaliser (ce qui diffère de la précédente est la zone marquée cidessous, ceci est dû à l’utilisation de l’interface Runnable)
public class PingPong_VThread {
public static void main(String[] args) {
// Créer le « moniteur » gérant la synchronisation de l'application
// Il est le même pour tous les joueurs (sinon pas de synchro)
Table laTable = new Table();
}
finally {
verrou.unlock();
}
}
// Creer les deux comportements
Joueur comportementJoueurPong = new Joueur(laTable, 0);
Joueur comportementJoueurPing = new Joueur(laTable, 1);
// Et les deux threads associés
Thread joueurPing = new Thread(comportementJoueurPing);
Thread joueurPong = new Thread(comportementJoueurPong);
public void libererAccesTable() {
verrou.lock();
try {
// On a fini de jouer, c'est à l'autre, on le réveille
peutJouer[(vientDeJouer + 1)%2].signal();
occupee = false;
}
finally {
verrou.unlock();
}
}
// Lancer leur exécution joueurs
joueurPing.start();
joueurPong.start();
System.out.println("Fin lancement application ");
try {
joueurPing.join();
} catch (InterruptedException e1) {
e1.printStackTrace();
}
try {
joueurPong.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Fin application ");
}
Classe implantant l’interface Runnable de manière à construire un comportement de joueur qui joue Ping
ou Pong (ce comportement sera donné au thread créé). On pourrait aussi continuer à hériter de la classe
Thread.
public class Joueur implements Runnable {
private int monType;
private Table maTable;
public Joueur (Table uneTable, int typeJoueur) {
monType = typeJoueur;
maTable = uneTable;
System.out.println("Joueur " + monType + " defini");
}
}
}
// Implantation de run => comportement du joueur
public void run() {
int i;
for (i = 0; i < 10; i++) {
try {
maTable.demanderAccesTable(monType);
} catch (InterruptedException e) { e.printStackTrace(); }
System.out.println("** Joueur " + monType + ": je joue pour la
" + i + "me fois");
maTable.libererAccesTable();
}
}
}
Master 1 Informatique – Conception et programmation parallèle
6/10
Master 1 Informatique – Conception et programmation parallèle
Corrigé des TD (2ème partie)
7/10
Synchronisation équivalente mais conception plus « orientée objet » avec notamment
encapsulation des méthodes d’un thread
// Table.java
/*
* Moniteur qui correspond à une partie
*/
package pingpong;
Corrigé des TD (2ème partie)
}
// Joueur.java
/*
* La classe Joueur implante le comportement des joueurs Ping et Pong
*/
package pingpong;
public class Joueur implements Runnable {
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.Condition;
static public final int PING = 0;
static public final int PONG = 1;
public class Table {
private
private
private
private
private
private
private
private
ReentrantLock l;
Condition peutjouer[];
int tour;
Boolean dedans;
public Table() {
l = new ReentrantLock();
peutjouer = new Condition[2];
peutjouer[Joueur.PING] = l.newCondition();
peutjouer[Joueur.PONG] = l.newCondition();
tour = Joueur.PING;
dedans = false;
}
public void demanderAccesTable(int typeJoueur) {
l.lock();
try {
while (tour != typeJoueur || dedans)
try {
peutjouer[typeJoueur].await();
} catch (InterruptedException e) {
e.printStackTrace();
}
dedans = true;
} finally {
l.unlock();
}
}
public void libererAccesTable(int typeJoueur) {
l.lock();
try {
tour = (tour + 1) % 2;
peutjouer[tour].signal();
dedans = false;
} finally {
l.unlock();
}
}
Master 1 Informatique – Conception et programmation parallèle
int type;
Table table;
Thread t;
int nb;
public int getNb() {
return nb;
}
public void setNb(int nb) {
this.nb = nb;
}
public Joueur(int type, Table table) {
this.type = type;
this.table = table;
t = new Thread(this);
nb = 5;
}
public Joueur(int type, Table table, int nb) {
this.type = type;
this.table = table;
t = new Thread(this);
this.nb = nb;
}
public Thread getThread() {
return t;
}
public void run() {
for (int i = 0; i < nb; ++i) {
table.demanderAccesTable(type);
System.out.println(type == PING ? "PING" : "PONG");
table.libererAccesTable(type);
}
}
Master 1 Informatique – Conception et programmation parallèle
8/10
Corrigé des TD (2ème partie)
public void attend_fin() throws InterruptedException {
t.join();
}
public void lancer() {
if (!t.isAlive())
t.start();
}
9/10
Corrigé des TD (2ème partie)
// application.java
package pingpong;
public class application {
}
// Application.java
package pingpong;
}
public class Application {
// Joueurs instanciés : JoueurPing.java et JoueurPong.java
/*
* Joueur Ping
*/
package pingpong;
public static final int NB = 10;
public static void main(String[] args) {
Table table = new Table();
JoueurPing ping[] = new JoueurPing[NB];
JoueurPong pong[] = new JoueurPong[NB];
public class JoueurPing extends Joueur {
public JoueurPing(Table table) {
super(Joueur.PING, table);
}
for (int i = 0; i < NB; ++i) {
ping[i] = new JoueurPing(table, 10);
pong[i] = new JoueurPong(table, 10);
}
public JoueurPing(Table table, int nb) {
super(Joueur.PING, table, nb);
}
for (int i = 0; i < NB; ++i)
ping[i].lancer();
for (int i = 0; i < NB; ++i)
pong[i].lancer();
}
/*
* Joueur Pong
*/
package pingpong;
for (int i = 0; i < NB; ++i)
try {
ping[i].attend_fin();
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i < NB; ++i)
try {
pong[i].attend_fin();
} catch (InterruptedException e) {
e.printStackTrace();
}
public class JoueurPong extends Joueur {
public JoueurPong(Table p) {
super(Joueur.PONG, p);
}
public JoueurPong(Table p, int nb) {
super(Joueur.PONG, p, nb);
}
}
}
}
Master 1 Informatique – Conception et programmation parallèle
Master 1 Informatique – Conception et programmation parallèle
10/10