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