public void - Parallele und verteilte Systeme

Werbung
PARALLELE S YSTEME
Ü BUNG 9, 18.06.08
Ü BERBLICK :
➜ Deadlocks
➜ Philosophenproblem
➜ Lösung mit Butler (Semaphor)
➜ Thread-Synchronisation und Java-Performance
➜ Deadlocks automatisch verhindern mit dem
DeadlockDetectingLock
Ü BUNG 9, 18.06.08
1
C OFFMAN , E LPHICK & S HOSHANI : D EADLOCK B EDINGUNGEN
Vier Bedingungen:
➀ 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
C OFFMAN , E LPHICK & S HOSHANI : D EADLOCK B EDINGUNGEN
2
DAS P HILOSOPHENPROBLEM
➜ 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.
DAS P HILOSOPHENPROBLEM
3
B UTLER IN JAVA
➜ Butler entspricht einem Semaphor
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();
}
}
B UTLER IN JAVA
4
F ORK . JAVA – M UTEX
class Fork {
private boolean inUse=false;
private int number;
private PhilPanel philPanel;
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);}});
}
F ORK . JAVA – M UTEX
5
public synchronized void put() {
inUse=false;
EventQueue.invokeLater(new Runnable() {
public void run() {
philPanel.drawFork(philPanel.getGraphics(),
true,
number);}});
notify();
}
}
F ORK . JAVA – M UTEX
6
P HILT HREAD . JAVA – KONSTANTEN , VARIABLEN , KONSTRUKTOR
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;
private Fork left, right;
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 – K ONSTANTEN , VARIABLEN , K ONSTRUKTOR
7
P HILT HREAD . JAVA – RUN ()
➜ Zwischen allen Aktionen natürlich noch ein entsprechendes
sleep()
private void draw(int state) {
EventQueue.invokeLater(new Runnable() {
public void run() {
philPanel.drawPhil(philPanel.getGraphics(),
state, number);}});
}
public void run() {
while(true) {
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 – RUN ()
}
}
8
P HIL PANEL . JAVA – VARIABLEN , PAINT C OMPONENT, KONSTRUKTOR
private final static int N=5;
private Fork[] forks=new Fork[N];
private PhilThread[] phils=new PhilThread[N];
private int[] philState=new int[N];
private boolean[] forkState=new boolean[N];
private Butler butler;
private Image[] imPhil=new Image[5];
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawOval(125, 90, 100, 100);
for(int i=0; i<N; i++) {
drawPhil(g, philState[i], i);
drawFork(g, forkState[i], i);
}
}
P HIL PANEL . JAVA – VARIABLEN , PAINT C OMPONENT, K ONSTRUKTOR
9
public PhilPanel( ) {
imPhil[PhilThread.EATING]=Toolkit.
getDefaultToolkit().getImage("eating.gif");
imPhil[PhilThread.GOTRIGHT]=Toolkit.
getDefaultToolkit().getImage("gotright.gif");
imPhil[PhilThread.HUNGRY]=Toolkit.
getDefaultToolkit().getImage("hungry.gif");
imPhil[PhilThread.SITTING]=Toolkit.
getDefaultToolkit().getImage("thinking.gif");
butler=new Butler(N-1);
for(int i=0; i<N; i++) {
forks[i]=new Fork(this, i);
forkState[i]=true; }
for(int i=0; i<N; i++) {
philState[i]=PhilThread.AWAY;
phils[i]=new PhilThread(forks[i],
forks[((i-1)+N)%N], butler, this, i);
phils[i].start( );
}
}
P HIL PANEL . JAVA – VARIABLEN , PAINT C OMPONENT, K ONSTRUKTOR
10
P HIL PANEL . JAVA – DRAW F ORK
public void drawFork(Graphics g,boolean state,
int fork) {
int x=0, y=0; forkState[fork]=state;
switch(fork) {
case 0: x=170; y=100; break;
case 1: x=200; y=120; break;
case 2: x=190; y=160; break;
case 3: x=150; y=160; break;
case 4: x=140; y=120; break;
}
if(g!=null) {
if(state==true) {
g.setColor(Color.black);
g.fillOval(x,y,10,10);
} else g.clearRect(x,y,10,10);
P HIL PANEL . JAVA – DRAW F ORK
}}
11
P HIL PANEL . JAVA – DRAW P HIL
public void drawPhil(Graphics g, int state,
int phil) {
int x=0, y=0; philState[phil]=state;
switch (phil) {
case 0: x=50; y=0; break;
case 1: x=200; y=0; break;
case 2: x=250; y=100; break;
case 3: x=125; y=200; break;
case 4: x=0; y=100; break;
}
if(g!=null) {
g.clearRect(x,y,100,90);
if(state!=PhilThread.AWAY)
g.drawImage(imPhil[state], x, y, null); }}
P HIL PANEL . JAVA – DRAW P HIL
12
S YNCHRONISIATION UND P ERFORMANCE
➜ Ü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
➜ Um auf der sicheren Seite zu sein, sind Modifikatormethoden
zustandsbehafteter Objekte, i.a. synchronisiert
➜ Ein gängiger Trick“ Flaschenhälse zu verhindern, ist die
”
Verwendung sog. synchronized-wrapper
S YNCHRONISIATION UND P ERFORMANCE
13
S YNCHRONIZED -W RAPPERS
➜ Synchronized-Wrappers ermöglichen selektiv unsynchronisierten
Zugriff auf Modifikatoren
public interface Adder {
public void add(int aNumber);
}
public class UnsyncedAdder implements Adder
{
int total, numAdditions;
public void add(int aNumber)
total += aNumber;
{
numAdditions++;
}
}
public class SyncedAdder implements Adder
{
Adder adder;
public SyncedAdder(Adder a)
{
adder = a;
public synchronized void add(int aNumber)
}
{ adder.add(aNumber); }
}
S YNCHRONIZED -W RAPPERS
14
S YNCHRONIZED -W RAPPERS IN DEN C OLLECTION API S
Beispiel:
List myUnsaveList = new ArrayList(CAPACITY);
List mySaveList = Collections.synchronizedList(myUnsaveList);
// Alternative: Collections.unmodifiableList
parallelMutator.execute(mySaveList);
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%
➜ Quelle: Java Performance Tuning“, Jack Shirazi, O’Reilly 2003
”
S YNCHRONIZED -W RAPPERS IN DEN C OLLECTION API S
15
S YNCHRONISATION GEZIELT VERWENDEN
➜ Beachte: Jede einzelne Akquisition eines Schlosses kann den
Anwendungsfluss erheblich drosseln
➜ Beispiel: fetch-emthode einer ungeschickten
Cache-Implementierung:
public Object fetch(String key) {
if(! myHashtable.containsKey(key)) {
myHashtable.put(key, createNewEntry(key));
}
return myHashtable.get(key);
}
S YNCHRONISATION GEZIELT VERWENDEN
16
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!
public synchonized Object fetch(String key) {
if(! myHashMap.containsKey(key)) {
myHashMap.put(key, createNewEntry(key));
}
return myHashMap.get(key);
}
➜ Ein Flaschenhals aufgrund ungeschickter Synchronisation wird
auch als Thread-Contention Problem bezeichnet
➜ Thread-Contention Probleme lassen sich mit Profilierungs-Tools
aufspühren.
➜ siehe z.B.:
http://freshmeat.net/projects/contentionprofiler
HREAD -C ONTENTION VERHINDERN
17
D EADLOCKFREIE S PERREN
➜ Ein Thread der fortwährend auf ein Schloß wartet, wird als
hard-waiting klassifiziert (analog: soft-waiting mit timeout)
➜ Jedes Schloß, das ein Thread in Besitz nimmt wird registriert
➜ Weiterhin speichern wir zu jedem Schloss alle
hard-waiting-Threads, so daß ein Wartebaum entsteht
➜ 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
Quelle: http://www.onjava.com (O’Reilly online)
public class DeadlockDetectedException extends RuntimeException {
public DeadlockDetectedException (String s) {
super(s);
}
}
D EADLOCKFREIE S PERREN
18
D EADLOCK D ETECTING L OCK
zyklische Definition einer Verklemmung (Deadlock):
Ein Schloss mit Verklemmung ist ein Schloss, das auf
einen Thread mit Verklemmung wartet.
Ein Thread mit Verklemmung ist ein Thread, der auf ein
Schloss mit Verklemmung wartet.
• Technisch gesehen, kann ein Schloss keinen Thread
besitzen: aktiv ist nur der Thread, der auf ein Schloss
wartet
• Wir unterscheiden nicht bei Beziehungen zwischen
Schlössern und Threads
D EADLOCK D ETECTING L OCK
19
V ERWALTUNG DER S CHL ÖSSER UND T HREADS
2 Listen:
Schlösser, die ein Thread hält
Alle Schlösser, die ein Thread hält, sind in
der DeadlockLockRegistry enthalten
DeadlockLockRegistry
Threads, die auf ein Schloß warten
Zu jedem Schloss gibt es eine Liste aller zugehörigen hardwaitingThreads
hardwaitingThreads
➜ Ein Thread besitzt ein Schloss, das wartende Threads hat, die
Schlösser haben, auf die andere Threads warten usw.
➜ Durch Beziehungen zwischen Schlössern und Threads wird ein
Baum beschrieben: der Wartebaum
V ERWALTUNG DER S CHL ÖSSER UND T HREADS
20
S CHLOSSSUCHE IM WAR TEBAUM
Prinzip: Deadlocks automatisch verhindern
Wartebaum
Ein Deadlock tritt auf, wenn ein Thread
auf ein Schloss wartet, das in seinem
Wartebaum enthalten ist.
Bevor ein Thread beginnt auf ein neues
Schloss zu warten, prüfen wir, dass
dieses Schloss nicht im Wartebaum
gespeichert ist (rekursive Tiefensuche)
canThreadwaitOnLock
➜ Die die Lock Klassen in Java 5 ermöglichen die Definition von
Sperren, bei denen eine Solche Suche implizit durchgeführt wird
➜ ReentrantLock wird erweitert und lock, lockInterruptibly
und tryLock werden überschrieben
S CHLOSSSUCHE IM WARTEBAUM
21
D EADLOCK D ETECTING L OCK IN JAVA
import java.util .*;
import java.util.concurrent .*;
import java.util.concurrent .locks .*;
public class DeadlockDetectingLock extends ReentrantLock {
private static List deadlockLocksRegistry = new ArrayList( );
private static synchronized void registerLock(DeadlockDetectingLock ddl) {
if (!deadlockLocksRegistry.contains(ddl))
deadlockLocksRegistry.add(ddl);
}
private static synchronized void unregisterLock(DeadlockDetectingLock ddl) {
if ( deadlockLocksRegistry.contains(ddl))
deadlockLocksRegistry.remove(ddl );
}
D EADLOCK D ETECTING L OCK IN JAVA
22
D EADLOCK D ETECTING L OCK , CONT.
private List hardwaitingThreads = new ArrayList( );
private static synchronized void markAsHardwait(List l, Thread t) {
if (!l.contains(t))
l.add(t); }
private static synchronized void freeIfHardwait(List l, Thread t) {
if (l.contains(t)) l.remove (t); }
private static Iterator getAllLocksOwned (Thread t) {
DeadlockDetectingLock current;
ArrayList results = new ArrayList( );
Iterator itr = deadlockLocksRegistry.iterator( );
while (itr.hasNext( )) {
current = (DeadlockDetectingLock)itr.next( );
if (current.getOwner( ) == t)
results.add(current);
}
return results.iterator( );
}
private static Iterator getAllThreadsHardwaiting(DeadlockDetectingLock l) {
return l.hardwaitingThreads.iterator( );
D EADLOCK D ETECTING L OCK , CONT.
}
23
D EADLOCK D ETECTING L OCK , CONT.
private static synchronized boolean
canThreadWaitOnLock(Thread t, DeadlockDetectingLock l) {
Iterator locksOwned = getAllLocksOwned(t);
while (locksOwned.hasNext( )) {
DeadlockDetectingLock current =
(DeadlockDetectingLock)locksOwned.next( );
if (current == l)
return false;
Iterator waitingThreads = getAllThreadsHardwaiting(current);
while (waitingThreads .hasNext( )) {
Thread otherthread = (Thread)waitingThreads.next( );
if (!canThreadWaitOnLock(otherthread, l)) {
return false;
}
}
return true;
}
D EADLOCK D ETECTING L OCK , CONT.
24
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");
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.
25
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) {
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.
26
Herunterladen