übung 9, 18.06.08 - Parallele und verteilte Systeme

Werbung
PARALLELE S YSTEME
Ü BUNG 9, 18.06.08
DAS P HILOSOPHENPROBLEM
Ü BERBLICK :
➜ Deadlocks
de 1
Slide 3
➜ Philosophenproblem
➜ 5 Philosophen speisen mit 5 Gabeln am runden Tisch, wobei
jeder zum Essen 2 Gabeln braucht. Ein Philosoph kommt zu
einem zufälligen Zeitpunkt an seinen Platz, nimmt zwei Gabeln
und ißt zufällig lange.
➜ Eine Lösung des Philosophen Problems besteht darin, dass ein
Butler nur maximal vier Philosophen gestattet, am Tisch
gleichzeitig Platz zu nehmen. Ein Philosoph muss vor dem Essen
erst Platz nehmen, und wenn er fertig ist, steht er auf.
➜ Lösung mit Butler (Semaphor )
➜ Thread-Synchronisation und Java-Performance
➜ Deadlocks automatisch verhindern mit dem
DeadlockDetectingLock
C OFFMAN , E LPHICK & S HOSHANI : D EADLOCK B EDINGUNGEN
B UTLER
Vier Bedingungen:
de 2
JAVA
➜ Butler entspricht einem Semaphor
➀ Serielle Benutzung von Resourcen (gegenseitiger Ausschluss)
Z.B.: Monitore mit synchronized Methoden
➁ Inkrementelles Resourcen-Erlangen, d.h. während des Wartens
auf eine Resource gibt der Prozeß die bereits erlangten
Resourcen nicht frei
Z.B.: Bei verschachtelten Monitoren behält Consumer den Lock,
während er auf den Producer wartet
➂ Keine Preemption, d.h. dem Prozeß kann die bereits erlangte
Resource nicht genommen werden
Z.B.: Wird dem Consumer der Lock genommen, kann der
Producer weiter arbeiten und somit gibt es kein Deadlock
➃ Wartezyklus: eine Prozeßkette existiert, wo jeder Prozeß die
Resource hält, auf die sein Vorgänger wartet
Z.B.: Consumer hält den Lock, den Producer benötigt; Producer
hält das neue Symbol, den Consumer benötigt
DAS P HILOSOPHENPROBLEM
IN
Slide 4
1
class Butler {
private int value;
public Butler(int i) { value=i; }
synchronized public void sitdown() {
while(value==0)
try { wait(); }
catch(InterruptedException e) {}
--value;
}
synchronized public void arise() {
++value;
notify();
}
}
F ORK . JAVA – M UTEX
2
F ORK . JAVA – M UTEX
P HILT HREAD . JAVA – KONSTANTEN , VARIABLEN , KONSTRUKTOR
class Fork {
private boolean inUse=false;
private int number;
private PhilPanel philPanel;
class PhilThread extends Thread {
public static int AWAY=0;
public static int HUNGRY=1;
public static int GOTRIGHT=2;
public static int EATING=3;
public static int SITTING=4;
public Fork(PhilPanel p, int n) {
number=n; philPanel=p; }
public synchronized void get() {
while(inUse)
try { wait(); }
catch(InterruptedException e) {}
inUse=true;
EventQueue.invokeLater(new Runnable() {
public void run() {
philPanel.drawFork(philPanel.getGraphics(),
false,
number);}});
}
ide 5
private Fork left, right;
Slide 7
private Butler butler;
private PhilPanel philPanel;
private int number;
public PhilThread(Fork l, Fork r,
Butler b, PhilPanel p, int n) {
left=l; right=r; butler=b; philPanel=p; number=n;
}
...
P HILT HREAD . JAVA –
RUN ()
➜ Zwischen allen Aktionen natürlich noch ein entsprechendes
sleep()
private void draw(int state) {
public synchronized void put() {
EventQueue.invokeLater(new Runnable() {
inUse=false;
public void run() {
EventQueue.invokeLater(new Runnable() {
philPanel.drawPhil(philPanel.getGraphics(),
public void run() {
philPanel.drawFork(philPanel.getGraphics(),
de 6
state, number);}});
Slide 8
true,
number);}});
}
public void run() {
while(true) {
notify();
draw(AWAY);
}
butler.sitdown(); draw(HUNGRY);
}
right.get(); draw(GOTRIGHT);
left.get(); draw(EATING);
left.put(); draw(GOTRIGHT);
right.put(); draw(SITTING);
butler.arise();
P HILT HREAD. JAVA – K ONSTANTEN , VARIABLEN , K ONSTRUKTOR
3
}
}
P HIL PANEL . JAVA – VARIABLEN , PAINT C OMPONENT, K ONSTRUKTOR
4
P HIL PANEL . JAVA – VARIABLEN ,
PAINT C OMPONENT,
P HIL PANEL . JAVA –
KONSTRUKTOR
DRAW F ORK
public void drawFork(Graphics g,boolean state,
private final static int N=5;
private Fork[] forks=new Fork[N];
int fork) {
private PhilThread[] phils=new PhilThread[N];
int x=0, y=0; forkState[fork]=state;
private int[] philState=new int[N];
switch(fork) {
private boolean[] forkState=new boolean[N];
case 0: x=170; y=100; break;
private Butler butler;
case 1: x=200; y=120; break;
private Image[] imPhil=new Image[5];
Slide 11
de 9
public void paintComponent(Graphics g) {
case 2: x=190; y=160; break;
case 3: x=150; y=160; break;
super.paintComponent(g);
case 4: x=140; y=120; break;
g.drawOval(125, 90, 100, 100);
if(g!=null) {
for(int i=0; i<N; i++) {
}
if(state==true) {
drawPhil(g, philState[i], i);
g.setColor(Color.black);
drawFork(g, forkState[i], i);
g.fillOval(x,y,10,10);
}
} else g.clearRect(x,y,10,10);
}
P HIL PANEL . JAVA –
public PhilPanel( ) {
}}
DRAW P HIL
imPhil[PhilThread.EATING]=Toolkit.
public void drawPhil(Graphics g, int state,
getDefaultToolkit().getImage("eating.gif");
int phil) {
imPhil[PhilThread.GOTRIGHT]=Toolkit.
int x=0, y=0; philState[phil]=state;
getDefaultToolkit().getImage("gotright.gif");
imPhil[PhilThread.HUNGRY]=Toolkit.
switch (phil) {
getDefaultToolkit().getImage("hungry.gif");
case 0: x=50; y=0; break;
imPhil[PhilThread.SITTING]=Toolkit.
case 1: x=200; y=0; break;
getDefaultToolkit().getImage("thinking.gif");
e 10
Slide 12
butler=new Butler(N-1);
case 2: x=250; y=100; break;
case 3: x=125; y=200; break;
for(int i=0; i<N; i++) {
forks[i]=new Fork(this, i);
case 4: x=0; y=100; break;
forkState[i]=true; }
}
for(int i=0; i<N; i++) {
if(g!=null) {
philState[i]=PhilThread.AWAY;
g.clearRect(x,y,100,90);
phils[i]=new PhilThread(forks[i],
forks[((i-1)+N)%N], butler, this, i);
phils[i].start( );
P HIL PANEL . JAVA – DRAW F ORK
}
if(state!=PhilThread.AWAY)
g.drawImage(imPhil[state], x, y, null); }}
}
5
S YNCHRONISIATION UND P ERFORMANCE
6
S YNCHRONIZED -W RAPPERS
IN DEN
C OLLECTION API S
Beispiel:
S YNCHRONISIATION
UND
List myUnsaveList = new ArrayList(CAPACITY);
List mySaveList = Collections.synchronizedList(myUnsaveList);
P ERFORMANCE
// Alternative: Collections.unmodifiableList
➜ Übermäßige Synchronisation erweist sich oft als Flaschenhals“
”
➜ Beim Entwurf einer Klasse lässt sich jedoch selten vorhersehen,
ob Instanzen von mehreren Threads zugleich genutzt werden
e 13
parallelMutator.execute(mySaveList);
Slide 15
➜ Um auf der sicheren Seite zu sein, sind Modifikatormethoden
zustandsbehafteter Objekte, i.a. synchronisiert
Performance-Messungen:
JVM 1.2
JVM 1.4
JVM 1.4 -Xint
Vector
100%
20%
134%
ArrayList
15%
14%
166%
Wrapped ArrayList
131%
26%
231%
➜ Ein gängiger Trick“ Flaschenhälse zu verhindern, ist die
”
Verwendung sog. synchronized-wrapper
➜ Quelle: Java Performance Tuning“, Jack Shirazi, O’Reilly 2003
”
S YNCHRONIZED -W RAPPERS
➜ Synchronized-Wrappers ermöglichen selektiv unsynchronisierten
Zugriff auf Modifikatoren
S YNCHRONISATION
public void add(int aNumber);
}
e 14
public class UnsyncedAdder implements Adder
int total, numAdditions;
public void add(int aNumber)
total += aNumber;
GEZIELT VERWENDEN
➜ Beachte: Jede einzelne Akquisition eines Schlosses kann den
Anwendungsfluss erheblich drosseln
➜ Beispiel: fetch-emthode einer ungeschickten
Cache-Implementierung:
public interface Adder {
{
Slide 16
{
public Object fetch(String key) {
if(! myHashtable.containsKey(key)) {
numAdditions++;
myHashtable.put(key, createNewEntry(key));
}
}
}
public class SyncedAdder implements Adder
return myHashtable.get(key);
{
}
Adder adder;
public SyncedAdder(Adder a) { adder = a; }
public synchronized void add(int aNumber) { adder.add(aNumber); }
}
S YNCHRONIZED -W RAPPERS IN DEN C OLLECTION API S
7
T HREAD -C ONTENTION VERHINDERN
8
T HREAD -C ONTENTION
VERHINDERN
➜ Die Implementierung des Caches, lässt sich optimieren, indem
eine unsynchronisierte HashMap verwendet wird
➜ Beachte: In diesem Fall ist die fetch-Methode zu
synchronisieren!
D EADLOCK D ETECTING L OCK
zyklische Definition einer Verklemmung (Deadlock):
Ein Schloss mit Verklemmung ist ein Schloss, das auf
public synchonized Object fetch(String key) {
einen Thread mit Verklemmung wartet.
if(! myHashMap.containsKey(key)) {
Ein Thread mit Verklemmung ist ein Thread, der auf ein
myHashMap.put(key, createNewEntry(key));
}
e 17
Slide 19
return myHashMap.get(key);
}
Schloss mit Verklemmung wartet.
• Technisch gesehen, kann ein Schloss keinen Thread
besitzen: aktiv ist nur der Thread, der auf ein Schloss
wartet
➜ Ein Flaschenhals aufgrund ungeschickter Synchronisation wird
auch als Thread-Contention Problem bezeichnet
➜ Thread-Contention Probleme lassen sich mit Profilierungs-Tools
aufspühren.
• Wir unterscheiden nicht bei Beziehungen zwischen
Schlössern und Threads
➜ siehe z.B.:
http://freshmeat.net/projects/contentionprofiler
D EADLOCKFREIE S PERREN
V ERWALTUNG
➜ Ein Thread der fortwährend auf ein Schloß wartet, wird als
hard-waiting klassifiziert (analog: soft-waiting mit timeout)
S CHL ÖSSER
UND
T HREADS
2 Listen:
Schlösser, die ein Thread hält
➜ Jedes Schloß, das ein Thread in Besitz nimmt wird registriert
Alle Schlösser, die ein Thread hält, sind in
der DeadlockLockRegistry enthalten
➜ Weiterhin speichern wir zu jedem Schloss alle
hard-waiting-Threads, so daß ein Wartebaum entsteht
e 18
DER
➜ Bevor ein Thread eine neue hard-waiting-Operation auf einem
Schloß durchgeführen kann, prüfen wir in einem Tiefendurchlauf,
ob das Schloß bereits im Wartebaum dieses Threads enthalten
ist und ggf. eine DeadlockDetectedException) ausgelöst
➜ Ein circular-wait ist somit ausgeschlossen
DeadlockLockRegistry
Slide 20
Threads, die auf ein Schloß warten
Zu jedem Schloss gibt es eine Liste aller zugehörigen hardwaitingThreads
Quelle: http://www.onjava.com (O’Reilly online)
hardwaitingThreads
public class DeadlockDetectedException extends RuntimeException {
public DeadlockDetectedException (String s) {
➜ Ein Thread besitzt ein Schloss, das wartende Threads hat, die
Schlösser haben, auf die andere Threads warten usw.
super(s);
}
➜ Durch Beziehungen zwischen Schlössern und Threads wird ein
Baum beschrieben: der Wartebaum
}
D EADLOCK D ETECTING L OCK
9
S CHLOSSSUCHE IM WARTEBAUM
10
S CHLOSSSUCHE
IM
D EADLOCK D ETECTING L OCK ,
WAR TEBAUM
private List hardwaitingThreads = new ArrayList( );
Prinzip: Deadlocks automatisch verhindern
private static synchronized void markAsHardwait(List l, Thread t) {
Wartebaum
if (!l.contains(t)) l.add(t); }
private static synchronized void freeIfHardwait(List l, Thread t) {
Ein Deadlock tritt auf, wenn ein Thread
auf ein Schloss wartet, das in seinem
if (l.contains(t)) l.remove (t); }
Wartebaum enthalten ist.
private static Iterator getAllLocksOwned (Thread t) {
DeadlockDetectingLock current;
ArrayList results = new ArrayList( );
Bevor ein Thread beginnt auf ein neues
Schloss zu warten, prüfen wir, dass
e 21
Slide 23
Iterator itr = deadlockLocksRegistry.iterator( );
dieses Schloss nicht im Wartebaum
while (itr.hasNext( )) {
current = (DeadlockDetectingLock)itr.next( );
if (current.getOwner( ) == t)
gespeichert ist (rekursive Tiefensuche)
results.add(current);
canThreadwaitOnLock
}
➜ Die die Lock Klassen in Java 5 ermöglichen die Definition von
Sperren, bei denen eine Solche Suche implizit durchgeführt wird
return results.iterator( );
}
private static Iterator getAllThreadsHardwaiting(DeadlockDetectingLock l) {
➜ ReentrantLock wird erweitert und lock, lockInterruptibly
und tryLock werden überschrieben
D EADLOCK D ETECTING L OCK
IN
return l.hardwaitingThreads.iterator( );
}
D EADLOCK D ETECTING L OCK ,
JAVA
CONT.
private static synchronized boolean
import java.util .*;
import java.util.concurrent .*;
import java.util.concurrent .locks .*;
canThreadWaitOnLock(Thread t, DeadlockDetectingLock l) {
Iterator locksOwned = getAllLocksOwned(t);
while (locksOwned.hasNext( )) {
DeadlockDetectingLock current =
public class DeadlockDetectingLock extends ReentrantLock {
(DeadlockDetectingLock)locksOwned.next( );
if (current == l)
private static List deadlockLocksRegistry = new ArrayList( );
e 22
CONT.
private static synchronized void registerLock(DeadlockDetectingLock ddl) {
return false;
Slide 24
Iterator waitingThreads = getAllThreadsHardwaiting(current);
if (!deadlockLocksRegistry.contains(ddl))
while (waitingThreads .hasNext( )) {
Thread otherthread = (Thread)waitingThreads.next( );
deadlockLocksRegistry.add(ddl);
}
if (!canThreadWaitOnLock(otherthread, l)) {
return false;
private static synchronized void unregisterLock(DeadlockDetectingLock ddl) {
}
if ( deadlockLocksRegistry.contains(ddl))
}
deadlockLocksRegistry.remove(ddl );
return true;
}
D EADLOCK D ETECTING L OCK , CONT.
}
11
D EADLOCK D ETECTING L OCK , CONT.
12
D EADLOCK D ETECTING L OCK ,
CONT.
public DeadlockDetectingLock ( ) { this(false, false); }
public DeadlockDetectingLock (boolean fair) { this(fair , false); }
private boolean debugging ;
public DeadlockDetectingLock (boolean fair , boolean debug) {
super(fair); debugging = debug ; registerLock (this); }
public void lock( ) {
if (isHeldByCurrentThread( )) {
if(debugging) System.out.println("already own lock");
e 25
super.lock( );
freeIfHardwait(hardwaitingThreads, Thread.currentThread( ));
return; }
markAsHardwait(hardwaitingThreads , Thread.currentThread( ));
if (canThreadWaitOnLock(Thread.currentThread( ), this)) {
if (debugging ) System.out.println("waiting for lock");
super.lock( );
freeIfHardwait(hardwaitingThreads , Thread.currentThread( ));
if (debugging) System.out.println ("got new lock");
} else { throw new DeadlockDetectedException("DEADLOCK"); }}
D EADLOCK D ETECTING L OCK ,
CONT.
➜ Die Methode sumArray hat bei ungünstiger Threadkoordination
einen Deadlock zur Folge:
public int sumArrays(int [ ] a1, int [ ] a2)
{
int value = 0;
int size = a1.length;
if (size == a2.length) {
e 26
synchronized(a1) {
synchronized(a2) {
for (int i = 0;
i < size;
++i)
value += a1 [ i ] + a2 [ i ];
}
}
}
return value;
}
D EADLOCK D ETECTING L OCK , CONT.
13
Herunterladen