Sie sind auf Seite 1von 5

1 Definitionen: Konstrukte

1.1 Thread Auf ein Java-Programm übertragen könnte ein Deadlock zu-
stande kommen, wenn sich zwei Monitore in den Zugangs-
• Fasst eine sequentielle Abfolge von Operationen zu ei-
methoden gegenseitig aufrufen können. In diesem Fall kann
nem Block zusammen
es vorkommen, dass ein Thread den Monitor A betritt und
• Modelliert das Verhalten dieses Blockes in Abhängig- ein anderer den Monitor B. Wenn nun in der Zugangsmetho-
keit von der Umgebung de von A eine Zugangsmethode von B aufgerufen wird und
in B eine von A, so wartet der erste Thread darauf, dass der
• Besitzt eigene Ressourcen (private data) zweite den Monitor B verlässt, dieser wartet jedoch darauf,
• Kann Ressourcen mit anderen Threads teilen (shared dass der erste den Monitor A verlässt.
data) Die Abwesenheit eines Deadlocks ist eine “Safety Property”.

1.2 Process 1.4.2 Livelock

Ein Prozess besteht aus mind. einem Thread mit mind. ei- Eine spezielle Art des Deadlocks. Ein Prozess A wartet auf
nem Adressraum (private + shared data). den Lock eines anderen Prozesses B, welcher wiederum auf
eine Eingabe des Thread A wartet. ⇒ Starvation ⇒ es wer-
1.3 Atomic Operation den die ganze Zeit Locks hin-und-her geschoben.

Eine Operation ist elementar (atomic), wenn sie durch


nichts unterbrochen werden kann. 1.5 Guard
Um zu signalisieren, wer was will, werden Guards eingesetzt.
• i := i++ ist nicht elementar (es passieren Zwischen- Guards können auch signalisieren, wer was darf. Ein Guard
schritte bei der Ausführung) kann ein Lock sein, wenn er signalisiert, ob ein Codeblock
ausgeführt werden darf, oder nicht.
• i := 1 ist elementar (in der Theorie)
Schematisches Beispiel
1.4 Lock select {
Locks regeln den Zugriff auf geteilte Ressourcen (shared da- guard1 -> {block1}
ta). Sie sperren den Zugriff auf eine kritische Resource für guard2 -> {block2}
alle Threads ausser einem (exclusive access). Für technische }
Umsetzungen siehe unten: Monitor, Semaphoren.
Wenn ein Guard true ist, dann wird sein zugehöriger Block
In Java werden Monitore und Intrensic Locks (speziell als
ausgeführt.
Locks integrierte Variablen) benutzt. Das zugehörige Stich-
Wenn mehrere Guards true ergeben, wird irgendein Block
wort ist synchronized.
ausgeführt
Es gibt kein Buffering und die Host-Schleife muss nicht ter-
1.4.1 Deadlock (Safety-Property) minieren.
Ein Deadlock (Verklemmung) ist ein Zustand von Prozes-
sen, bei dem mindestens zwei Prozesse untereinander auf Beispiel: Brand eines Hauses (Guard) verlangt nach einem
Betriebsmittel warten, die dem jeweils anderen Prozess zu- Feuerwehrauto. Aber ohne Autoschlüssel (Lock) kann man
geteilt sind. nicht mit dem Feuerwehrauto fahren.

1
2 Definitionen: Eigenschaften
2.1 Safety-Property • Kein Thread darf für immer warten.
(gutes) immer erfüllt oder (schlechtes) nie erfüllt
Ein beliebiges Objekt ist thread-save, wenn sein Status von 2.5 Fairness (liveness-property)
einem anderen Objekt aus gesehen immer gültig ist. Es • Weak Fairness: Wenn ein Thread dauernd um eine
darf niemals etwas unvorhergesehenes passieren. Wenn eine Ressource bettelt, bekommt er sie.
Safety-Property nicht erfüllt ist, geht das ganze Universum
kapputt ;-) • Strong Fairness: Wenn ein Thread genug lange um
eine Ressource bettelt, bekommt er sie.
2.2 Liveness-Property • Linear waiting: Wenn ein Thread eine Ressource
irgendwann erfüllt verlangt, bekommt er sie, bevor ein anderer Thread
Liveness beschreibt die Wohldefiniertheit eines Programms. sie zweimal bekommt.
Wenn die Liveness-Property nicht erfüllt ist, kann es unter
• FIFO waiting: Wenn ein Thread eine Ressource ver-
Umständen sein, dass das Programm keine richtigen Resul-
langt, bekommt er sie vor einem anderen Thread, der
tate liefert.
sie nachher verlangt hat.

2.3 Performance
2.6 Synchronisation
Ein paralleles Programm sollte immer schneller sein, als die
• Stelle sicher, dass Thread-Safety gewährleistet ist. Je-
entsprechende sequentielle Variante. Die Beziehung der Ge-
des gemeinsam verwendete Objekt muss einen gülti-
schwindigkeit (speed-up) zwischen der parallelen und der
gen Status haben (java: synchronized): siehe Safety,
sequentiellen Variante kann z.B. durch Amdahl’s Law be-
Monitor, Semaphoren
schrieben werden.
• Übermittle alle Änderungen einer Variablen an alle
2.4 Termination beteiligten Threads (java: volatile).

• Der “Organisator“ (host, server) eines parallelen Pro- • Verhindere Dead Locks (“tote Winkel“).
gramms muss nicht terminieren. Er darf Threads so-
lange starten und stoppen, wie er will (“infinite” server • Stelle sicher, dass keine zufälligen Resultate zustande
loops). kommen (race conditions) ⇒ Die Synchronisation
muss korrekt sein, d.h.: es dürfen keine stale values
• Alle einzelnen Threads jedoch müssen fortschreiten. (veraltete Werte) gelesen und verarbeitet werden.

3 Konzepte
3.1 Critical Section eventuell nicht mehr aktuelle Werte (stale values) gelesen.
Somit ist ein Programm mit einer race condition nicht
Bezeichnet einen Codeabschnitt, der nur von einem Thread
thread-safe.
auf einmal usgeführt werden darf.

3.2 Starvation 3.4 Mutual Exclusion (Mutex)


Der Lese-Thread bekommt keine “Nahrung“ mehr. Starva- • Das Prinzip des Mutex (gegenseiten Ausschlusses) be-
tion tritt ein, wenn der Thread-Host seine Threads schlecht zeichnet einen Lösungsansatz dafür, dass eine Critical
verwaltet und z.B. einem anderen Thread Schreibzugriffe Section effektiv von nur einem Thread auf einmal ab-
auf eine Ressource gibt, dem Lese-Thread aber kein Zeit- gearbeitet wird.
fenster zum lesen.
• Es dürfen keine Deadlocks und keine Starvation auf-
3.3 Race Condition treten.

Die “Happens before Relationship” ist ungeordnet. Es ist • Die Implementation der Mutex darf keine Delays und
nicht klar, was der Reihe nach gemacht wird. Es werden wenig Overhead erzeugen

2
4 Ansätze für Mutual Exclusion
4.1 Dekker Solution Bsp.: Definition einer Semaphore
Verwirklicht Mutual Exclusion und Fairness (FIFO Wai- class Semaphore {
ting) in einfachster Weise (allerdings nur für zwei Threads). int s = 0;
Idee: Es werden “Wächter“ eingesetzt, die kontrollieren, Semaphore(int init) { s = init; }
ob ihr bewachter Codeblock ausgeführt werden darf. Dabei synchronized void p() {
wird ein Flag ausgetauscht. Da nach jeder Ausführung ei- while (s==0) wait();
nes Codeblocks ein anderer Codeblock, der die Ausführung s = s-1;
beantragt hat, an die Reihe kommt, ist die Dekker Solution }
starvation free. synchronized void v() {
s = s + 1;
Codebeispiel von Wikipedia }
}
boolean flag0 = false;
boolean flag1 = false;
int turn = 0; // alternativ: turn = 1 Bsp.: Gebrauch einer Semaphore

// Prozess 0 Semaphore S = new Seamphote(1);


p0: flag0 = true; semaphore.p();
while (flag1) { // critical section
if (turn != 0) { semaphore.v();
flag0 = false;
while (turn != 0) {
}
Vor jeder Critical Section wird S.p(); vom einen und
flag0 = true; S.v(); vom andern Thread aufgerufen.
}
} 4.3 Monitor
// kritischer Abschnitt
turn = 1; Ein Monitor ist eine Lösung für Mutual Exclusion. Ein Mo-
flag0 = false; nitor (java: synchronized) verwaltet die Zugriffe auf geteil-
te Ressourcen von “ausserhalb“. Er teilt Locks von Objekten
// Prozess 1 den Threads anhand ihrer Prioritäten zu. Im Gegensatz zu
p1: flag1 = true; den Semaphoren muss sich ein Programmierer nicht um den
while (flag0) { Monitor kümmern. Er kann seine kritischen Objekte einfach
if (turn != 1) { als kritisch deklarieren (java: synchronized). Der Monitor
flag1 = false;
übernimmt dann die Zuweisung der Threads.
while (turn != 1) {
}
flag1 = true; 4.4 CSP: Communicating Sequential Pro-
} cesses
}
// kritischer Abschnitt • CSP ist ein Prinzip zur Parallelisierung von Prozessen,
turn = 0; das ohne geteilte Ressourcen auskommt.
flag1 = false;
• Grundsätzlich kann CSP als eine Anwendung von Mu-
Jeder Prozess wartet in einer leeren Schleife, bis er an die tual Exclusion angesehen werden.
Reihe kommt. • Die Prozesse steuern sich quasi selbst.
• Das einzige, was geteilt ist, sind sind sogenannte
4.2 Semaphoren (Lichtsignale) Channels. Über diese kommunizieren die Prozesse mit-
Eine Semaphore stellt eine Erweiterung der Dekker’s So- einander.
lution für mehr als 2 Threads dar. Dabei enthält eine Se- • Die Channels sind (standardmässig) nicht buffered.
maphore eine Queue, in welcher alle Threads eingetragen
werden, die eine gewisse Ressource verlangen. Die Sema- • Reader und Writer tauschen Signale über ihre Stati
phore arbeitet diese Queue der Reihe nach ab und gibt den aus. Erst wenn der Reader bereit ist, sendet der Wri-
wartenden Thread kontrollierten Zugriff auf die Ressour- ter seinen Output.
ce. Schwachstelle dieses Mechanismus: Der Programmierer • CSP setzt das Consumer-Producer-Szenario um.
muss die Semaphoren konsequent und korrekt einsetzen.
• S ≥ 0 und S = S0 + #V − #P Consumer Wartet, wenn keine Nachricht über den Kanal
kommt, sonst liest er (java: get(ch,data)).
• Anz. Threads in der Semaphore = Anfangsfüllung +
Anz. Requests + Anz. Releases Producer Wartet, wenn der Consumer noch nicht bereit
ist, sonst sendet er (java: put(ch,data)).

3
5 Proofing
5.1 Speed-Up (Amdahl’s Law) 5.3 Beweise (Notation siehe PP FS09 Sli-
• ignoriert Overhead des 10:6)
Gehe nach folgendem Schema vor:
• ignoriert Load Imbalance

Durch die Einteilung eines Programms in sequentiell und


• gegeben: States ermitteln und Labels setzen
parallel ausführbare Blöcke, kann berechnet werden, wie die
einzelnen Blöcke a propos Geschwindigkeit im Verhältnis • zu zeigen: Invarianten aufstellen
zueinander stehen. Es interessiert vorallem der Speed-Up
eines sequentiellen Programmes, das parallelisiert wird. Ei- • Beweis: Invarianten Beweisen
ne Idee zur Berechnung des Speed-Up’s ist Amdahl’s Law
(N := Anz. Prozessoren): Benutze einfache Definitionen und einigermassen logische
Folgerungen um die Invarianten zu beweisen, so im Stil von:
Laufzeit des Programms = 1 = (1 − P ) + P Wenn das passiert, kann das nicht mehr passieren, etc.
| {z } |{z}
sequentieller Teil parallelisierter Teil
5.3.1 Semaphoren erfüllen Mutex
1 1
Speed-Up = S = P
≤ #T : Anz. Threads in der Critical Section
(1 − P ) + N
1−P
S: Binäre Semaphore mit S0 = 1
Wenn S gegeben ist, dann entspricht N dem Speed-Up. #T == #P (S) − #V (S)
S == S0 + #V − #P
5.2 Linearisierbarkeit S == 1 − #T
#T + S == 1
Eine History (geordnete Abfolge von Requests und zugehöri-
gen Responses) ist linearisierbar, wenn:
5.3.2 Keine Starvation
• ihre Requests und Responses so umgeordnet werden
können, dass sie immer noch eine History bilden. Zeige Zusammenhänge der Flags

• eine Umordnung eine gültige Abfolge von Requests 5.3.3 Kein Deadlock
und Responses ergibt (auf einen Request folgt eine
Response) Beweise, dass nicht beide Flags auf Null

• eine Umordnung gültig im Sinne der Korrektheit des


Programmes ist (z.B.: Ein Schreiben kommt immer 5.3.4 Vorhandensein einer Race Condition
vor einem Lesen). Konkret heisst das, das Programm Mache ein Beispiel und überlege die Lower- und Upper-
darf nicht mit einer Response beginnen. Bound

6 Java Structures
6.1 Exceptions try..catch..finally-Block wird nicht ausgeführt, wenn
eine Exception auftritt.
6.1.1 Finally statement

6.1.2 Exception-Typen
try { Checked exceptions Das Programm bewegt sich im ge-
...
planten Umfeld.
} catch (Exception e) {
...
Unchecked exceptions Es treten Fehler auf, die nicht
} finally {
abgefangen wurden.
...
}
Errors Exceptions, die nicht vom Programm
abhängen, sondern von der Laufzeitumgebung.
Auf solche Fehler kann nicht immer reagiert wer-
Wird im try-Block eine Exception geworfen, so wird die den.
Ausführung des try-Blockes unterbrochen. Die Exception
kann dann mit catch abgefangen werden, wobei es Sinn Runtime Exceptions Die Logik des Programmes
macht, verschiedene Exception-Typen zu unterscheiden. Der wurde verletzt. Kann eventuell durch hinzufügen
finally-Block wird immer ausgeführt. Der Code nach dem von Fehlerlogik beseitigt werden.

4
6.2 Multithreading wait
6.2.1 Thread-Zustände Stoppt einen Thread für eine angegebene Zeit. Der zugehöri-
ge Monitor fährt fort.
• NEW: Thread wurde neu erstellt

• RUNNABLE: Thread wird ausgeführt sleep


Stoppt einen Thread für eine angegebene Zeit. Der zugehöri-
• WAITING: Thread warted, bis er aufgerufen wird ge Monitor wird auch angehalten.

• TIMED_WAITING: Thread wartet, eine gewisse Zeit lang


6.2.3 synchronized
6.2.2 Handling Klasse, Variable oder Funktion wird als Atomic Ope-
ration angesehen. synchronized garantiert thread-safety
NotifyAll für ein einzelnes Objekt. Ein Monitor überwacht die
Weckt alle Threads. Einer davon wird ausgeführt. synchronized-Ressourcen und vergibt Locks an die
Threads, welche die Ressource verlangen.

Notify 6.2.4 volatile


Weckt einen Threads und führt ihn aus.
Deklariert eine Ressource, die für alle Threads immer den
aktuellen Wert enthält. volatile ist quasi eine globale Va-
run
riable für Threads, was aber nicht automatisch Thread-
Startet den Thread. Safety implizert.

7 OpenMP
OpenMP automatisiert die Parallelisierung eines linearen private(var): definiert die Variable var als private, was
Programmes. Der Programmierer definiert gewisse Bereiche, heisst, dass var nur einem Thread auf einmal gehört
die er parallelisiert haben will. OpenMP entscheidet dann (vermeidet race conditions).
selbständig, wie das Programm im gegebenen Environment
ausgeführt wird. critical: definiert eine Critical Section des Threads

schedule(runtime|dynamic|guided|static): Automa-
tisiertes Load-Balancing. Threads, die mit ihren jewei-
7.1 Syntax: Java
ligen Aufträgen fertig sind, können anderen zur Hilfe
Die OpenMP Deklarationen werden in Kommentaren zwi- kommen.
schen den eigentlichen Programmcode geschrieben. So wird
sichergestellt, dass der parallelisierte Code kompatibel mit Bsp.: Anwendung
seiner lineare Variante ist (z.B. für Compiler, die kein int i,var;
OpenMP unterstützen). int[] A,B,C;
Für uns wichtig sind folgende Befehle: int n=1000;
// omp parallel for private(var)
for (i=0; i<n; i++) {
parallel: der folgende Code wird parallelisiert var = A[i] + B[i];
C[i] = var * var ;
for: deklariert, dass eine Schleife folgt (die gewissen Richt- }
linien entspricht)