1 Parallélisme
Eric Goubault
Commissariat à l'Energie Atomique
Saclay
2 Plan du cours
- Ordonnancement de tâches JAVA
- Le problème de l'exclusion mutuelle
- synchronized, wait() et notify()
- Sémaphores
- Sémantique, interblocage et séquentialisation
- Simulation de passage de messages
- Contrôle avancé de threads, application: barrières de synchronisation
3 Etats d'un thread
Un thread peut être dans l'un
des 4 cas suivants:
- l'état initial, entre sa création et start().
- l'état prêt, immédiatement après start.
- l'état bloqué: thread
en attente, d'un verrou, d'un socket, quand
il est suspendu (sleep(long)
- l'état terminaison, quand run() se termine ou
quand on invoque stop() (à éviter si possible... n'existe plus en JAVA 2).
4 Etats d'un thread
- Détermination de l'état:
isAlive(), qui renvoie un booléen indiquant si un thread est encore
vivant, c'est à dire s'il est prêt ou bloqué
-
On peut aussi interrompre l'exécution d'un thread qui est prêt (passant
ainsi dans l'état bloqué).
void interrupt() envoie une interruption au thread spécifié;
si pendant sleep, wait ou
join,
lèvent une exception InterruptedException.
5 Etats d'un thread
6 Priorités
- Priorité = nombre entier,
qui plus il est grand, plus le processus est prioritaire.
-
void setPriority(int priority) assigne une priorité et
int getPriority() renvoie la priorité.
-
La priorité peut être maximale: Thread.MAX_PRIORITY, normale (par défaut):
Thread.NORM_PRIORITY (au minimum elle vaut 0).
7 Processus démons
On peut également déclarer un processus comme étant un démon ou
pas:
setDaemon(Boolean on);
boolean isDaemon();
``support'' aux autres
(horloge, ramasse-miettes...). Il est
détruit quand il n'y a plus aucun processus utilisateur (non-démon) restant
8 Ordonnancement tâches (cas 1 processeur)
-
Le choix du thread JAVA à exécuter (partiellement): parmi les threads qui sont prêts.
-
Ordonnanceur JAVA: ordonnanceur préemptif basé
sur la priorité des processus.
-
``Basé sur la priorité'':
essaie de rendre actif le(s)
thread(s) prêt(s) de plus haute
priorité.
- ``Préemptif'': interrompt le thread courant de priorité moindre,
qui reste néanmoins prêt.
9 Ordonnancement de tâches
-
Un thread actif qui devient bloqué, ou qui termine rend
la main à un autre thread, actif, même s'il est de priorité moindre.
- La spécification de JAVA ne définit pas précisément la
façon
d'ordonnancer des threads de même
priorité, par exemple:
- ``round-robin'' (ou ``tourniquet''):
un compteur interne fait alterner l'un après l'autre (pendant des périodes
de temps prédéfinies) les processus prêts de même priorité ¾®
assure l'équité;
aucune famine (plus tard...)
-
Ordonnancement plus classique (mais pas équitable)
thread actif ne peut pas être
préempté par un thread prêt de même priorité. Il faut que ce dernier
passe en mode bloqué.
(par
sleep() ou plutôt
static void yield())
10 En fait...
L'ordonnancement
dépend aussi bien de l'implémentation de la JVM que du système
d'exploitation sous-jacent; 2 modèles principaux:
- ``green thread'', c'est la JVM qui implémente l'ordonnancement
des threads qui lui sont associés (donc pas d'exploitation
multiprocesseur - souvent UNIX par défaut).
- ``threads natifs'', c'est
le système d'exploitation hôte de la JVM qui effectue l'ordonnancement
des threads JAVA (par défaut Windows).
11 Encore des comptes...
public class Compte {
private int valeur;
Compte(int val) {
valeur = val;
}
public int solde() {
return valeur;
}
12
public void depot(int somme) {
if (somme > 0)
valeur+=somme;
}
public boolean retirer(int somme)
throws InterruptedException {
if (somme > 0)
if (somme <= valeur) {
Thread.currentThread().sleep(50);
valeur -= somme;
Thread.currentThread().sleep(50);
return true;
}
return false;
} }
13 La banque...
public class Banque implements Runnable {
Compte nom;
Banque(Compte n) {
nom = n; }
public void Liquide (int montant)
throws InterruptedException {
if (nom.retirer(montant)) {
Thread.currentThread().sleep(50);
14
Donne(montant);
Thread.currentThread().sleep(50); }
ImprimeRecu();
Thread.currentThread().sleep(50); }
public void Donne(int montant) {
System.out.println(Thread.currentThread().
getName()+": Voici vos " + montant + " francs."); }
public void ImprimeRecu() {
if (nom.solde() > 0)
System.out.println(Thread.currentThread().
getName()+": Il vous reste " + nom.solde() + " francs.");
else
System.out.println(Thread.currentThread().
getName()+": Vous etes fauches!");
15
}
public void run() {
try {
for (int i=1;i<10;i++) {
Liquide(100*i);
Thread.currentThread().sleep(100+10*i);
}
} catch (InterruptedException e) {
return;
}
}
public static void main(String[] args) {
Compte Commun = new Compte(1000);
16
Runnable Mari = new Banque(Commun);
Runnable Femme = new Banque(Commun);
Thread tMari = new Thread(Mari);
Thread tFemme = new Thread(Femme);
tMari.setName("Conseiller Mari");
tFemme.setName("Conseiller Femme");
tMari.start();
tFemme.start();
}
}
17 Une exécution
% java Banque
Conseiller Mari: Voici vos 100 francs.
Conseiller Femme: Voici vos 100 francs.
Conseiller Mari: Il vous reste 800 francs.
Conseiller Femme: Il vous reste 800 francs.
Conseiller Mari: Voici vos 200 francs.
Conseiller Femme: Voici vos 200 francs.
Conseiller Femme: Il vous reste 400 francs.
Conseiller Mari: Il vous reste 400 francs.
Conseiller Mari: Voici vos 300 francs.
Conseiller Femme: Voici vos 300 francs.
Conseiller Femme: Vous etes fauches!
Conseiller Mari: Vous etes fauches! ...
18 Résultat...
- Le mari a retiré 600 francs du compte commun,
- La femme a retiré 600 francs du compte commun,
- qui ne contenait que 1000 francs au départ!
19 Explication
(``Sémantique'' -- certes idéalisée...)
- L'exécution de plusieurs threads se fait en exécutant une action insécable
(``atomique'') de l'un des threads, puis d'un autre ou d'éventuellement du même
etc.
- Tous les ``mélanges'' possibles sont permis
20 Sémantique par entrelacements
21 Explication
Si les 2 threads tMari et tFemme sont exécutés de telle
façon que
dans retirer, chaque étape soit faite en même temps:
- Le test pourra trouvé être satisfait par les deux threads en même temps,
- qui donc retireront en même temps de l'argent.
22 Sémantique
Langage simple, ``fragment de JAVA'':
EXPR |
::= |
CONSTANTE |
| |
VARIABLE |
|
| |
EXPR+EXPR |
| |
EXPR*EXPR |
|
| |
EXPR/EXPR |
| |
EXPR-EXPR |
TEST |
::= |
EXPR==EXPR |
| |
EXPR < EXPR |
|
| |
EXPR > EXPR |
| |
TEST && TEST |
|
| |
TEST | | TEST |
| |
! TEST |
INSTR |
::= |
VARIABLE=EXPR |
23 Sémantique
BLOCK |
::= |
e |
|
| |
INSTR;BLOCK |
|
| |
if TEST |
|
|
then BLOCK |
|
|
else BLOCK ;BLOCK |
|
| |
while TEST |
|
|
BLOCK ; |
|
|
BLOCK |
24 Systèmes de transitions
Sémantique d'un BLOCK inductivement sur la syntaxe,
en lui associant ce que l'on appelle un système de transitions:
4-uplet (S,i,E,Tran) où,
- S est un ensemble d'états,
- i Î S est l'``état initial'',
- E est un ensemble d'étiquettes,
- Tran Í S × E × S est l'ensemble des transitions.
On note plus communément (s,t,s') Î Tran comme s ®t s'.
25 Sémantique
-
Environnement: toute
fonction de VARIABLE vers l'ensemble des entiers relatifs.
- Un état
du système de transition sera une paire (l,r)
où l Î L (ce qu'il reste à exécuter)
et r est un environnement.
26 Sémantique
27 Sémantique
28 Entrelacements
Ensemble de processus P1,...,Pn Î L. ``Entrelacements'':
- Pj ¾®
(Sj,ij,Ej,Tranj),
- (S,i,E,Tran)=[ [ P1 | ... | Pn ] ] par:
- S=S1 × ... × Sn, i=(i1,...,in),
- E=E1 È ... È En,
- Tran={(s1,...,sj,...,sn) ®tj
(s1,...,sj',...,sn) où
sj ®tj sj' Î Tranj}
29 Exécutions
- Les exécutions ou traces ou entrelacements dans cette sémantique sont des suites
[finies]:
telles que si-1 ®ti si Î Tran
pour tout i entre 1 et n.
-
Si les 2 threads tMari et tFemme sont exécutés de telle
façon que
dans retirer, chaque étape soit faite en même temps, alors
le test pourra être satisfait par les deux threads en même temps,
qui donc retireront en même temps de l'argent.
30 Une solution
31 Maintenant...
% java Banque
Conseiller Mari: Voici vos 100 francs.
Conseiller Mari: Il vous reste 800 francs.
Conseiller Femme: Voici vos 100 francs.
Conseiller Femme: Il vous reste 800 francs.
Conseiller Mari: Voici vos 200 francs.
Conseiller Mari: Il vous reste 400 francs.
Conseiller Femme: Voici vos 200 francs.
Conseiller Femme: Il vous reste 400 francs.
Conseiller Femme: Il vous reste 100 francs.
Conseiller Mari: Voici vos 300 francs.
Conseiller Mari: Il vous reste 100 francs.
Conseiller Femme: Il vous reste 100 francs.
Conseiller Mari: Il vous reste 100 francs...
32 Résultat...
- Le mari a tiré 600 francs,
- La femme a tiré 300 francs,
- et il reste bien 100 francs dans le compte commun.
33 Peut on se passer de synchronized?
public class CS1 extends Thread {
Thread tour = null;
public void AttendtonTour() {
while (tour != Thread.currentThread()) {
if (tour == null)
tour = Thread.currentThread();
try {
Thread.sleep(100);
} catch (Exception e) {}
}
}
34
public void RendlaMain() {
if (tour == Thread.currentThread())
tour = null;
}
public void run() {
while(true) {
AttendtonTour();
System.out.println("C'est le tour de "+
Thread.currentThread().getName());
RendlaMain();
} }
35
public static void main(String[] args) {
Thread Un = new CS1();
Thread Deux = new CS1();
Un.setName("UN");
Deux.setName("DEUX");
Un.start();
Deux.start();
} }
Problème: exécution synchrone des threads!
36 wait() et notify()
Chaque objet fournit un verrou, mais aussi un mécanisme de mise en attente
(forme primitive de communication inter-threads; similaire aux variables de conditions
ou aux moniteurs):
- void wait() attend l'arrivée d'une condition sur l'objet sur lequel
il s'applique (en général this). Doit être
appelé depuis l'intérieur d'une méthode ou d'un bloc synchronized,
(il y a aussi une version avec timeout)
- void notify() notifie un thread en attente d'une condition, de
l'arrivée de celle-ci. De même, dans synchronized.
- void notify_all() même chose mais pour tous les threads en attente
sur l'objet.
37 Sémaphores
public class Semaphore {
int n;
String name;
public Semaphore(int max, String S) {
n = max;
name = S;
}
38
public synchronized void P() {
if (n == 0) {
try {
wait();
} catch(InterruptedException ex) {};
}
n--;
System.out.println("P("+name+")");
}
public synchronized void V() {
n++;
System.out.println("V("+name+")");
notify();
}
}
39 Section Critique
public class essaiPV extends Thread {
static int x = 3;
Semaphore u;
public essaiPV(Semaphore s) {
u = s;
}
public void run() {
int y;
// u.P();
40
try {
Thread.currentThread().sleep(100);
y = x;
Thread.currentThread().sleep(100);
y = y+1;
Thread.currentThread().sleep(100);
x = y;
Thread.currentThread().sleep(100);
} catch (InterruptedException e) {};
System.out.println(Thread.currentThread().
getName()+": x="+x);
// u.V();
}
41
public static void main(String[] args) {
Semaphore X = new Semaphore(1,"X");
new essaiPV(X).start();
new essaiPV(X).start();
}
}
42 Résultat (sans P, V)
% java essaiPV
Thread-2: x=4
Thread-3: x=4
43 Résultat (avec P, V)
% java essaiPV
P(X)
Thread-2: x=4
V(X)
P(X)
Thread-3: x=5
V(X)
44 Un peu de sémantique
On rajouter à L: P(VARIABLE) et
V(VARIABLE).
La sémantique par systèmes de transitions change assez peu:
- On rajoute dans état global S
une composante nouvelle: fonction k: VARIABLE ®
{0,1}.
- i=(P1,...,Pn,k0) où k0: fonction
uniforme = 1,
- E: nouvelles actions Px et Vx,
45 Sémantique
- nouvelles transitions:
(b1,...,Px;bj,...,bn,k) ®Px (b1,...,bj,...,bn,k')
si k(x)=1; et alors k'(y)=k(x) pour tout y ¹ x et
k'(x)=0, et,
(b1,...,Px;bj,...,bn,k) ®Vx (b1,...,bj,...,bn,k')
avec k'(y)=k(x) pour tout y ¹ x et
k'(x)=1.
L'apparition de ces nouvelles transitions contraint les entrelacements
possibles.
46 Une ``abstraction''
L'idée de base: ``Progress graphs'' (E.W.Dijkstra (1968))
T1=Pa.Pb.Vb.Va en parallèle avec T2=Pb.Pa.Va.Vb
``Image continue'': xi = temps local
Traces = chemins croissants dans chaque coordonnée = ``di-chemins''
47 Portée de synchronized
48 Pour être plus précis...
-
Toutes les variables (avec leurs verrous correspondants et la liste des
threads en attente d'accès)
de tous les threads sont stockées dans la mémoire
gérée par la JVM correspondante.
- Les variables locales
d'un thread ne peuvent pas être accédée par d'autres threads, donc on
peut imaginer que chaque thread a une mémoire locale et a accès à une
mémoire globale.
-
Pour efficacité, chaque thread a copie locale des variables
à utiliser ¾® mises à jour de la mémoire
principale.
49 Pour être plus précis
-
Plusieurs actions atomiques
peuvent être effectuées pour gérer ces copies locales
et la mémoire principale, au moins pour les types simples (int, float...).
- use transfère le contenu de la copie locale d'une variable à
la machine exécutant le thread.
- assign transfère une valeur de la machine exécutant le thread
à la copie locale d'une variable de ce même thread.
50 Pour être plus précis
- load affecte une valeur venant de la mémoire principale (après
un read) à la copie locale à un thread d'une variable.
- store transfère le contenu de la copie locale à un thread d'une
variable à la mémoire principale (qui va ainsi pouvoir faire un write).
- lock réclame un verrou associé à une variable à la mémoire
principale.
- unlock libère un verrou associé à une variable.
51 En fait...
Actions de la mémoire principale (la JVM):
- read transfère le contenu d'une variable de la mémoire principale
vers la mémoire locale d'un thread (qui pourra ensuite faire un load).
- write affecte une valeur transmise par la mémoire locale d'un
thread (par store) d'une variable dans la mémoire principale.
Pour les types ``plus longs'' comme double et long, la spécification
permet tout à fait que load, store, read et write
soient non-atomiques.
52 Contraintes
Pour un thread donné:
- à tout lock correspond plus tard une opération unlock.
- chaque opération load est associée à une opération read qui la précède,
- chaque store est associé à une
opération write qui la suit.
53 Contraintes
Pour une variable donnée et un thread donné,
-
l'ordre dans lequel des read
sont faits par la mémoire locale (respectivement les write) est le même
que l'ordre dans lequel le thread fait les load (respectivement les
store).
- Vrai que pour une variable donnée!
-
Pour les variables volatile,
aussi respecté entre les read/load et les write/store
entre les variables. Aussi:
use suivent immédiatement les load et store suivent
immédiatement les assign.
54 Exemples
class Essai1 extends Thread {
public Essai1(Exemple1 e) {
E = e; }
public void run() {
E.F1(); }
private Exemple1 E; }
55 Exemples
class Essai2 extends Thread {
public Essai2(Exemple1 e) {
E = e; }
public void run() {
E.G1(); }
private Exemple1 E; }
56 Exemples
class Lance {
public static void main(String args[]) {
Exemple1 e = new Exemple1();
Essai1 T1 = new Essai1(e);
Essai2 T2 = new Essai2(e);
T1.start();
T2.start();
System.out.println("From Lance a="+e.a+" b="+e.b); } }
57 Exemples
class Exemple1 {
int a = 1;
int b = 2;
void F1() {
a = b; }
void G1() {
b = a; } }
58 Exemples
class Exemple2 {
int a = 1;
int b = 2;
synchronized void F2() {
a = b; }
synchronized void G2() {
b = a; } }
59 Exécutions
> java Lance
a=2 b=2
> java Lance
a=1 b=1
> java Lance
a=2 b=1
60 Explication
Les chemins possibles d'exécution sont les entrelacements des 2 traces
séquentielles:
read b ® load b ® use b ® assign a ®
store a ® write a
pour le thread Essai1 et
read a ® load a ® use a ® assign b ®
store b ® write b
pour le thread Essai2.
61 Explication
Appelons a1, b1 (respectivement a2 et b2) les copies locales des variables
a et b pour le thread Essai1 (respectivement pour le thread Essai2):
write a ® read a ® read b ® write b
read a ® write a ® write b ® read b
read a ® write a ® read b ® write b
62 Explication
-
l'état de la machine est la1=2, lb1=2, a=2, b=2,
la2=2 et lb2=2,
- ou la1=1, lb1=1, a=1, b=1,
la2=1 et lb2=1.
- ou la1=2, lb1=2, a=2, b=1,
la1=1 et lb1=1.
Si on utilise la version synchronisée de Exemple1: Exemple2, alors on n'a plus que les deux premiers
ordonnancements qui soient possibles.
63 Philosophes qui dînent
64 Philosophes qui dînent
public class Phil extends Thread {
Semaphore LeftFork;
Semaphore RightFork;
public Phil(Semaphore l, Semaphore r) {
LeftFork = l;
RightFork = r;
}
65
public void run() {
try {
Thread.currentThread().sleep(100);
LeftFork.P();
Thread.currentThread().sleep(100);
RightFork.P();
Thread.currentThread().sleep(100);
LeftFork.V();
Thread.currentThread().sleep(100);
RightFork.V();
Thread.currentThread().sleep(100);
} catch (InterruptedException e) {};
}
}
66
public class Dining {
public static void main(String[] args) {
Semaphore a = new Semaphore(1,"a");
Semaphore b = new Semaphore(1,"b");
Semaphore c = new Semaphore(1,"c");
Phil Phil1 = new Phil(a,b);
Phil Phil2 = new Phil(b,c);
Phil Phil3 = new Phil(c,a);
Phil1.setName("Kant");
Phil2.setName("Heidegger");
Phil3.setName("Spinoza");
Phil1.start();
Phil2.start();
Phil3.start();
} }
67 Résultat
% java Dining
Kant: P(a)
Heidegger: P(b)
Spinoza: P(c)
^C
68 Interblocage
- Une exécution possible n'atteint jamais l'état terminal: quand
les trois philosophes prennent en même temps leur fourchette gauche.
- Peut se résoudre avec un ``ordonnanceur'' extérieur (exercice classique).
- On pouvait s'en apercevoir sur le ``request graph'' ou sur
le ``progress graph'': le point mort est du à l'intersection de 3 (=nombre
de processus) hyperrectangles...
69 Explication
/* 3 philosophers ``3p'' */
A=Pa.Pb.Va.Vb
B=Pb.Pc.Vb.Vc
C=Pc.Pa.Vc.Va
70 Une solution possible
-
Numéroter les philosophes,
-
Imposer que les philosophes
pairs acquièrent la fourchette gauche d'abord, puis la droite et
impairs acquièrent la fourchette droite d'abord,
puis la gauche.
71 Une solution
P1=Pa2.Pa1.Va2.Va1
P2=Pa2.Pa3.Va2.Va3
...
P2k=Pa2k.Pa(2k+1).Va2k.Va(2k+1)
P(2k+1)=Pa(2k+2).Pa(2k+1).Va(2k+2).Va(2k+1)
72 Sémaphores à compteur
public class essaiPVsup extends Thread {
static int x = 3;
Semaphore u;
public essaiPVsup(Semaphore s) {
u = s;
}
73
public void run() {
int y;
u.P();
try {
Thread.currentThread().sleep(100);
y = x;
Thread.currentThread().sleep(100);
y = y+1;
Thread.currentThread().sleep(100);
x = y;
Thread.currentThread().sleep(100);
} catch (InterruptedException e) {};
System.out.println(Thread.currentThread().
getName()+": x="+x);
u.V(); }
74
public static void main(String[] args) {
Semaphore X = new Semaphore(2,"X");
new essaiPVsup(X).start();
new essaiPVsup(X).start();
}
}
75 Résultat
% java essaiPVsup
Thread-2: P(X)
Thread-3: P(X)
Thread-2: x=4
Thread-2: V(X)
Thread-3: x=4
Thread-3: V(X)
Pas de protection!
76 Réel intérêt
Pour les tampons de capacité bornée (problème du type producteur consommateur):
public class essaiPVsup2 extends Thread {
static String[] x = {null,null};
Semaphore u;
public essaiPVsup2(Semaphore s) {
u = s;
}
77
public void push(String s) {
x[1] = x[0];
x[0] = s;
}
public String pop() {
String s = x[0];
x[0] = x[1];
x[1] = null;
return s; }
public void produce() {
push(Thread.currentThread().getName());
System.out.println(Thread.currentThread().
getName()+": push");
}
78
public void consume() {
pop();
System.out.println(Thread.currentThread().
getName()+": pop"); }
public void run() {
try {
u.P();
produce();
Thread.currentThread().sleep(100);
consume();
Thread.currentThread().sleep(100);
u.V();
} catch(InterruptedException e) {};
}
79
public static void main(String[] args) {
Semaphore X = new Semaphore(2,"X");
new essaiPVsup2(X).start();
new essaiPVsup2(X).start();
new essaiPVsup2(X).start();
}
}
80 Résultat
% java essaiPVsup2
Thread-2: P(X)
Thread-2: push
Thread-3: P(X)
Thread-3: push
Thread-2: pop
Thread-3: pop
Thread-2: V(X)
Thread-3: V(X)
Thread-4: P(X)
Thread-4: push
Thread-4: pop
Thread-4: V(X)
81 Sérialisation
public class Ticket {
String nom;}
public class Requete extends Thread {
Ticket Aller;
Ticket Retour;
Semaphore u;
Semaphore v;
public Requete(Ticket t, Ticket s,
Semaphore x, Semaphore y) {
Aller = t;
Retour = s;
u = x;
v = y; }
82
public void run() {
try {
u.P();
Thread.currentThread().sleep(100);
if (Aller.nom == null)
Aller.nom = Thread.currentThread().
getName();
Thread.currentThread().sleep(100);
u.V();
} catch (InterruptedException e) {};
83
try {
v.P();
Thread.currentThread().sleep(100);
if (Retour.nom == null)
Retour.nom = Thread.currentThread().
getName();
Thread.currentThread().sleep(100);
v.V();
} catch (InterruptedException e) {};
}
}
84
public class Serial {
public static void main(String[] args) {
Semaphore a = new Semaphore(1,"a");
Semaphore b = new Semaphore(1,"b");
Ticket s = new Ticket();
Ticket t = new Ticket();
Requete Bill = new Requete(s,t,a,b);
Requete Tom = new Requete(t,s,b,a);
Bill.setName("Bill");
Tom.setName("Tom");
Bill.start();
Tom.start();
try {
Tom.join();
Bill.join();
} catch(InterruptedException e) {};
85
System.out.println("Le billet Paris->New York
est au nom de: "+s.nom);
System.out.println("Le billet New York->Paris
est au nom de: "+t.nom); } }
86 Résultat...
% java Serial
Bill: P(a)
Tom: P(b)
Bill: V(a)
Tom: V(b)
Bill: P(b)
Tom: P(a)
Bill: V(b)
Tom: V(a)
Le billet Paris->New York est au nom de: Bill
Le billet New York->Paris est au nom de: Tom
87 Progress graph
88 2-phase locking
Changer dans Requete, l'ordre des verrous, dans la méthode run():
public void run() {
try {
u.P();
v.P();
Thread.currentThread().sleep(100);
if (Aller.nom == null)
Aller.nom = Thread.currentThread().
getName();
Thread.currentThread().sleep(100);
89
Thread.currentThread().sleep(100);
if (Retour.nom == null)
Retour.nom = Thread.currentThread().
getName();
Thread.currentThread().sleep(100);
u.V();
v.V();
} catch (InterruptedException e) {};
}
90 Résultat
% java Serial2
Bill: P(a)
Bill: P(b)
Bill: V(a)
Bill: V(b)
Tom: P(b)
Tom: P(a)
Tom: V(b)
Tom: V(a)
Le billet Paris->New York est au nom de: Bill
Le billet New York->Paris est au nom de: Bill
91 Mais...
Il est possible qu'une exécution donne également un interblocage...
% java Serial2
Bill: P(a)
Tom: P(b)
^C
92 Progress graph
Les seules obstructions sont ``au centre'': on peut déformer tout chemin sur l'un des bords.
93 Exemple d'utilisation
L'interface Serializable de JAVA:
- permet d'assurer la persistence de certains objets,
- utilisé en particulier pour les entrées sorties
94 Simulation de passage par messages
public class MsgQueue {
Vector queue = new Vector();
public synchronized void send(Object obj) {
queue.addElement(obj); }
public synchronized Object recv() {
if (queue.size() == 0)
return null;
Object obj = queue.firstElement();
queue.removeElementAt(0);
return obj; } }
95 Exemple
public class Process {
MsgQueue In;
MsgQueue Out;
public Process(MsgQueue i, MsgQueue o) {
In = i;
Out = o;
}
96
public void send(int x) {
Out.send(new Integer(x));
System.out.println(Thread.currentThread().
getName()+": send("+x+")"); }
public int recv() {
Object x = In.recv();
while (x == null)
x = In.recv();
int res = ((Integer) x).intValue();
System.out.println(Thread.currentThread().
getName()+": recv "+res);
return res; }
}
Réception bloquante, ``active polling''.
97 Exemple
public class essaiMsg extends Thread{
Process p;
public essaiMsg(Process q) {
p = q; }
public void run() {
for (int i=1;; i++) {
p.send(i);
p.recv(); } }
98
public static void main(String[] args) {
MsgQueue Canal1 = new MsgQueue();
MsgQueue Canal2 = new MsgQueue();
Process Tim = new Process(Canal1, Canal2);
Process Tom = new Process(Canal2, Canal1);
new essaiMsg(Tim).start();
new essaiMsg(Tom).start();
}
}
99 Résultat
% java essaiMsg
Thread-2: send(1)
Thread-3: send(1)
Thread-3: recv 1
Thread-3: send(2)
Thread-2: recv 1
Thread-2: send(2)
Thread-2: recv 2
Thread-2: send(3)
Thread-3: recv 2
^C
100 Barrières de synchronisation
(ref.: ``Java Threads'', O'Reilly)
public Barrier(int nThreads) {
threads2Wait4 = nThreads;
}
public synchronized int waitForRest()
throws InterruptedException {
int threadNum = --threads2Wait4;
101
if (iex != null) throw iex;
if (threads2Wait4 <= 0) {
notifyAll();
return threadNum;
}
while (threads2Wait4 > 0) {
if (iex != null) throw iex;
try {
wait();
} catch(InterruptedException ex) {
iex = ex;
notifyAll();
}
}
return threadNum;
}
102
public synchronized void freeAll() {
iex = new InterruptedException("Barrier Released
by freeAll");
notifyAll();
}
}
103 Exemple d'utilisation
public class ProcessIt implements Runnable {
String[] is;
Barrier bpStart, bp1, bp2, bpEnd;
public ProcessIt(String[] sources) {
is = sources;
bpStart = new Barrier(sources.length);
bp1 = new Barrier(sources.length);
bp2 = new Barrier(sources.length);
bpEnd = new Barrier(sources.length);
for (int i=0;i<is.length; i++) {
(new Thread(this)).start(); } }
104
public void run() {
try {
int i = bpStart.waitForRest();
doPhaseOne(is[i]);
bp1.waitForRest();
doPhaseTwo(is[i]);
bp2.waitForRest();
doPhaseThree(is[i]);
bpEnd.waitForRest();
} catch(InterruptedException ex) {};
}
public void doPhaseOne(String ps) {
}
105
public void doPhaseTwo(String ps) {
}
public void doPhaseThree(String ps) {
}
static public void main(String[] args) {
ProcessIt pi = new ProcessIt(args);
}
}
This document was translated from LATEX by HEVEA.