[gesichtete Version] | [gesichtete Version] |
Kwastg (Diskussion | Beiträge) Keine Bearbeitungszusammenfassung |
|||
(51 dazwischenliegende Versionen von 5 Benutzern werden nicht angezeigt) | |||
Zeile 1: | Zeile 1: | ||
Der folgende Quellcode ist bereits aus dem Kapitel [[Kritischer Abschnitt]] bekannt: | Der folgende Quellcode ist bereits aus dem Kapitel [[Kritischer Abschnitt]] bekannt: | ||
</p> | </p> | ||
<p id="Listing_1"> | <p id="Listing_1"> | ||
<loop_listing title="Listing 1: Beispiele für kritische und unkritische Abschnitte" description="Ein Java-Programm mit zwei Threads. Bei Thread_A wechseln sich ein unkritischer, ein kritischer und noch ein unkritischer Abschnitt ab."> | <loop_listing title="Listing 1: Beispiele für kritische und unkritische Abschnitte" description="Ein Java-Programm mit zwei Threads. Bei Thread_A wechseln sich ein unkritischer, ein kritischer und noch ein unkritischer Abschnitt ab." id="5fa9788954c67"> | ||
<source lang="java" line="true"> | <source lang="java" line="true"> | ||
public class Beispiel_Kritischer_Abschnitt { | public class Beispiel_Kritischer_Abschnitt { | ||
Zeile 61: | Zeile 58: | ||
</p> | </p> | ||
<br /> | |||
== Erläuterungen zu Listing 1 == | |||
<p> | |||
Zwei Threads A und B greifen jeweils auf die <span style="font-family:Courier">counter</span>-Variable zu. Die <span style="font-family:Courier">counter</span>-Variable stellt damit ein gemeinsam genutztes Betriebsmittel dar. | |||
</p> | |||
<p> | |||
In beiden Threads lassen sich kritische Abschnitte identifizieren. Es muss dafür gesorgt werden, dass sich immer nur ein Thread zur Zeit in seinem kritischen Abschnitt befindet. | |||
</p> | |||
<p> | |||
Dies nennt man den ''wechselseitigen Ausschluss'': Wenn sich ein Thread in seinem kritischen Abschnitt befindet, dann muss ausgeschlossen sein, dass auch der andere Thread in seinen kritischen Abschnitt eintritt. | |||
</p> | |||
<br /> | |||
<p> | |||
<loop_area type="notice"> | |||
<p> | |||
Du kennst bereits das [[Aktives Warten mit TSL|aktive Warten (mit TSL)]], durch das ein wechselseitiger Ausschluss realisiert werden kann. Jedoch verschwendet das aktive Warten CPU-Zeit. | |||
</p> | |||
<p> | |||
Ein Mutex kommt nun anstatt des aktiven Wartens zum Einsatz. Somit verschwindet auch der Nachteil der CPU-Zeitverschwendung. | |||
</p> | |||
</loop_area> | |||
</p> | |||
<br /> | |||
== Mutex in Listing 1 einbauen == | |||
<p> | |||
Um einen Mutex in Listing 1 einzubauen, muss diese Datenstruktur zunächst erzeugt werden, um anschließend die <span style="font-family:Courier">P()</span>- und <span style="font-family:Courier">V()</span>-Operationen an den geeigneten Stellen aufzurufen. | |||
</p> | |||
<p> | |||
Listing 2 zeigt dieses Vorgehen, direkt darunter gibt es einige Erläuterungen. | |||
</p> | |||
<br /> | |||
<p id="Listing_2"> | |||
<loop_listing title="Listing 2: Ergänzt um einen Mutex" description="" id="5fa9788954c77"> | |||
<source lang="java" line="true"> | |||
public class Wechselseitiger_Ausschluss_mit_Mutex { | |||
static int counter = 0; | |||
static Semaphore mutex = new Semaphore(1); | |||
public static class Thread_A extends Thread { | |||
public void run() { | |||
do_something(); // unkritisch | |||
count_from_10(); // kritisch !!! | |||
do_something_else(); // unkritisch | |||
} | |||
private void do_something() { | |||
// unkritischer Abschnitt | |||
System.out.println("Thread_A: unkritisch"); | |||
} | |||
private void count_from_10() { | |||
// Vorsicht: kritischer Abschnitt! | |||
P(mutex); | |||
counter = 10; | |||
counter++; | |||
counter++; | |||
System.out.println("A-Counter: " + counter); | |||
V(mutex); | |||
} | |||
private void do_something_else() { | |||
// unkritischer Abschnitt | |||
System.out.println("Thread_A: wieder unkritisch"); | |||
} | |||
} | |||
public static class Thread_B extends Thread { | |||
public void run() { | |||
System.out.println("Thread_B ist gestartet."); | |||
P(mutex); | |||
counter = 20; | |||
counter++; | |||
counter++; | |||
counter++; | |||
counter++; | |||
counter++; | |||
counter++; | |||
System.out.println("B-Counter: " + counter); | |||
V(mutex); | |||
} | |||
} | |||
public static void main(String[] args) { | |||
Thread a = new Thread_A(); | |||
Thread b = new Thread_B(); | |||
a.start(); | |||
b.start(); | |||
} | |||
} | |||
</source> | |||
</loop_listing> | |||
</p> | |||
<br /> | |||
== Erläuterungen zu Listing 2 mit Mutex == | |||
<p> | |||
Entscheidend sind hier die Zeilen 4, 19, 26, 38 und 49. | |||
</p> | |||
<p> | |||
In Zeile 4 wird zunächst eine Variable mit dem Namen <span style="font-family:Courier">mutex</span> vom Datentyp <span style="font-family:Courier">Semaphore</span> deklariert und initialisiert. Hier wird also ein binärer Semaphor (Mutex) erzeugt. | |||
</p> | |||
<p> | |||
Der kritische Abschnitt von Thread_A liegt in den Zeilen 21 bis 24.<br /> | |||
Direkt davor (in Zeile 19) wird die Methode <span style="font-family:Courier">P(mutex);</span> aufgerufen, direkt danach (in Zeile 26) wird die Methode <span style="font-family:Courier">V(mutex);</span> aufgerufen. | |||
</p> | |||
<p> | |||
Der kritische Abschnitt von Thread_B liegt in den Zeilen 40 bis 47.<br /> | |||
Direkt davor (in Zeile 38) wird die Methode <span style="font-family:Courier">P(mutex);</span> aufgerufen, direkt danach (in Zeile 49) wird die Methode <span style="font-family:Courier">V(mutex);</span> aufgerufen. | |||
</p> | |||
<br /> | |||
=== Mutex-Beispiel mit Threads === | |||
<p> | |||
<loop_area type="notice"> | |||
<p> | <p> | ||
Der hier abgedruckte Quellcode zu Listing 2 ist in dieser Form nicht ausführbar. Er soll lediglich beispielhaft verdeutlichen, an welchen Stellen zusätzliche Operationen im Bezug auf den Mutex zur Realisierung des wechselseitigen Ausschlusses eingefügt werden müssen. | |||
</p> | </p> | ||
<p> | <p> | ||
Zwar gibt es in Java eine Klasse <span style="font-family:Courier">Semaphore</span>, siehe<br /> | |||
<small>http://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Semaphore.html</small><br /> | |||
jedoch sind hier die <span style="font-family:Courier">P()</span> und <span style="font-family:Courier">V()</span>-Operationen anders benannt:<br /> | |||
<span style="font-family:Courier">acquire()</span> bzw. <span style="font-family:Courier">release()</span> | |||
</p> | </p> | ||
<p> | <p> | ||
Unter dem hier verlinkten Dokument findet sich bei Bedarf ein anderes sehr einfaches Java-Beispiel, welches den praktischen Einsatz der <span style="font-family:Courier">Semaphore</span>-Klasse zeigt. | |||
</p> | |||
</loop_area> | |||
</p> | </p> | ||
<br /> | |||
== Aufgabe 1 == | |||
<p> | <p> | ||
<loop_area type="task"> | |||
<loop_task title="Mutex in Action" id="5fa9788954c86"> | |||
<p> | |||
Spiele einmal gedanklich den Ablauf der beiden Threads aus [[Wechselseitiger_Ausschluss#Listing_2|Listing 2]] durch. | |||
</p> | |||
<p> | |||
Was passiert, wenn Thread_A bereits seinen kritischen Abschnitt betreten hat, und beispielsweise in Zeile 23 ein Kontextwechsel zu Thread_B erfolgt? | |||
</p> | |||
<p> | |||
* Thread_B möchte auch seinen kritischen Abschnitt betreten und ruft in Zeile 38 die <span style="font-family:Courier">P()</span>-Operation auf... Und dann? | |||
* Was passiert mit Thread-B? | |||
* Und wie kommt Thread-B da wieder raus? | |||
</p> | |||
</loop_task> | |||
</loop_area> | |||
</p> | |||
<br /> | |||
== Aufgabe 2 == | |||
<p> | |||
<loop_area type="task"> | |||
<loop_task title="Aktives Warten vs. Mutex" id="5fa9788954c94"> | |||
<p> | |||
Vergleiche den obigen [[Wechselseitiger_Ausschluss#Listing_2|Quellcode aus Listing 2 (Mutex)]] einmal mit dem bereits bekannten Quellcode mit dem Beispiel des Aktiven Wartens (mit while). | |||
</p> | |||
<br /> | |||
<p> | |||
<loop_listing title="Beispiel: Aktives Warten" description="Ein Java-Programm mit zwei Threads. Die Sperrvariable soll ein gleichzeitiges Betreten der kritischen Abschnitte durch beide Threads verhindern." id="5fa9788954ca2"> | |||
<source lang="java" line="true"> | |||
public class Beispiel_Aktives_Warten_mit_while { | |||
static int counter = 0; // gemeinsam genutztes Betriebsmittel | |||
static int lock = 0; // Sperrvariable | |||
public static class Thread_A extends Thread { | |||
public void run() { | |||
do_something(); // unkritisch | |||
count_from_10(); // kritisch !!! | |||
do_something_else(); // unkritisch | |||
} | |||
private void do_something() { | |||
// unkritischer Abschnitt | |||
System.out.println("Thread_A: unkritisch"); | |||
} | |||
private void count_from_10() { | |||
// Vorsicht: kritischer Abschnitt! | |||
while (lock == 1); // Semikolon beachten! | |||
lock = 1; | |||
counter = 10; | |||
counter++; | |||
counter++; | |||
System.out.println("A-Counter: " + counter); | |||
lock = 0; | |||
} | |||
private void do_something_else() { | |||
// unkritischer Abschnitt | |||
System.out.println("Thread_A: wieder unkritisch"); | |||
} | |||
} | |||
public static class Thread_B extends Thread { | |||
public void run() { | |||
System.out.println("Thread_B ist gestartet."); | |||
while (lock == 1); // Semikolon beachten! | |||
lock = 1; | |||
counter = 20; | |||
counter++; | |||
counter++; | |||
counter++; | |||
counter++; | |||
counter++; | |||
counter++; | |||
System.out.println("B-Counter: " + counter); | |||
lock = 0; | |||
} | |||
} | |||
public static void main(String[] args) { | |||
Thread a = new Thread_A(); | |||
Thread b = new Thread_B(); | |||
a.start(); | |||
b.start(); | |||
} | |||
} | |||
</source> | |||
</loop_listing> | |||
</p> | |||
<br /> | |||
<p> | |||
Hier ist ein Bild mit beiden Quelltexten nebeneinander: | |||
</p> | |||
<p> | |||
[[Datei:aktives_warten_vs_mutex.jpg|700px]] | |||
</p> | |||
</loop_task> | |||
</loop_area> | |||
</p> | |||
<br /> | |||
== Mutex mit Prozessen == | |||
<p> | |||
Die in [[Wechselseitiger_Ausschluss#Listing_2|Listing 2]] beispielhaft gezeigte Verwendung eines Mutex bezieht sich im Quellcode auf zwei Threads. Ein Mutex wird in Betriebssystemen aber üblicherweise eingesetzt, um die kritischen Abschnitte zweier Prozesse (mit einem gemeinsam genutzten Betriebsmittel) gegeneinander abzuschotten. | |||
</p> | |||
<br /> | |||
== Aufgabe 3 == | |||
<p> | |||
<loop_area type="task"> | |||
<loop_task title="Mutex und Prozesse bei Wikipedia" id="5fa9788954cb2"> | |||
<p> | |||
Wikipedia zeigt auf der Seite<br /> | |||
<small>http://de.wikipedia.org/wiki/Semaphor_%28Informatik%29#Anwendungsbeispiele</small><br /> | |||
im Abschnitt ''"Einsatz in Konkurrenzsituationen I"'' ein sehr einfach gehaltenes Beispiel mit zwei Prozessen und einem Mutex. | |||
</p> | |||
<p> | |||
Schau' es dir dort an! | |||
</p> | |||
</loop_task> | |||
</loop_area> | |||
</p> | |||
<br /> | |||
<p> | |||
Das Erzeugen eines Mutex sowie die <span style="font-family:Courier">P()</span>- und <span style="font-family:Courier">V()</span>-Operation kann man sich bei Betriebssystemen als Systemaufrufe vorstellen. Somit verwaltet das Betriebssystem die Warteschlange eines Semaphors und kann problemlos dafür sorgen, dass die <span style="font-family:Courier">P()</span>- und <span style="font-family:Courier">V()</span>-Operation [[Das_Problem_des_ungünstigsten_Moments#Definition:_Atomare_Aktion|atomar]] abläuft. Das Betriebssystem kann dann mit dem Scheduler die wartenden Prozesse in den Status "wartend" versetzen - was nicht möglich wäre, wenn die Programmiersprache selbst die Mutex-Verwaltung durchführen würde. | |||
</p> | |||
<br /> | |||
== Aufgabe 4 == | |||
<p> | |||
<loop_area type="task"> | |||
<loop_task title="Applet zum wechselseitigen Ausschluss" id="5fa9788954cbf"> | |||
<p> | |||
An der FH Köln wird ein [http://www.nt.fh-koeln.de/fachgebiete/inf/diplom/semwork/ Semaphor Workshop] mit Java-Applets bereitgestellt, anhand derer der wechselseitige Ausschluss mit Hilfe eines binären Semaphors (Mutex) nachvollzogen werden kann. Probiere es aus! | |||
</p> | |||
<p> | |||
<small>http://www.nt.fh-koeln.de/fachgebiete/inf/diplom/semwork/beispiele/waus/waus.html</small> | |||
</p> | |||
<p> | |||
<small>Falls das Java-Applet in deinem Browser nicht startet, musst du eventuell die [[Java-Applets|Java-Sicherheitseinstellungen]] anpassen. Trage dort ein: http://www.nt.fh-koeln.de</small> | |||
</p> | |||
</loop_task> | |||
</loop_area> | |||
</p> | </p> | ||
Der folgende Quellcode ist bereits aus dem Kapitel Kritischer Abschnitt bekannt:
public class Beispiel_Kritischer_Abschnitt {
static int counter = 0;
public static class Thread_A extends Thread {
public void run() {
do_something(); // unkritisch
count_from_10(); // kritisch !!!
do_something_else(); // unkritisch
}
private void do_something() {
// unkritischer Abschnitt
System.out.println("Thread_A: unkritisch");
}
private void count_from_10() {
// Vorsicht: kritischer Abschnitt!
counter = 10;
counter++;
counter++;
System.out.println("A-Counter: " + counter);
}
private void do_something_else() {
// unkritischer Abschnitt
System.out.println("Thread_A: wieder unkritisch");
}
}
public static class Thread_B extends Thread {
public void run() {
System.out.println("Thread_B ist gestartet.");
counter = 20;
counter++;
counter++;
counter++;
counter++;
counter++;
counter++;
System.out.println("B-Counter: " + counter);
}
}
public static void main(String[] args) {
Thread a = new Thread_A();
Thread b = new Thread_B();
a.start();
b.start();
}
}
Zwei Threads A und B greifen jeweils auf die counter-Variable zu. Die counter-Variable stellt damit ein gemeinsam genutztes Betriebsmittel dar.
In beiden Threads lassen sich kritische Abschnitte identifizieren. Es muss dafür gesorgt werden, dass sich immer nur ein Thread zur Zeit in seinem kritischen Abschnitt befindet.
Dies nennt man den wechselseitigen Ausschluss: Wenn sich ein Thread in seinem kritischen Abschnitt befindet, dann muss ausgeschlossen sein, dass auch der andere Thread in seinen kritischen Abschnitt eintritt.
Du kennst bereits das aktive Warten (mit TSL), durch das ein wechselseitiger Ausschluss realisiert werden kann. Jedoch verschwendet das aktive Warten CPU-Zeit.
Ein Mutex kommt nun anstatt des aktiven Wartens zum Einsatz. Somit verschwindet auch der Nachteil der CPU-Zeitverschwendung.
Um einen Mutex in Listing 1 einzubauen, muss diese Datenstruktur zunächst erzeugt werden, um anschließend die P()- und V()-Operationen an den geeigneten Stellen aufzurufen.
Listing 2 zeigt dieses Vorgehen, direkt darunter gibt es einige Erläuterungen.
public class Wechselseitiger_Ausschluss_mit_Mutex {
static int counter = 0;
static Semaphore mutex = new Semaphore(1);
public static class Thread_A extends Thread {
public void run() {
do_something(); // unkritisch
count_from_10(); // kritisch !!!
do_something_else(); // unkritisch
}
private void do_something() {
// unkritischer Abschnitt
System.out.println("Thread_A: unkritisch");
}
private void count_from_10() {
// Vorsicht: kritischer Abschnitt!
P(mutex);
counter = 10;
counter++;
counter++;
System.out.println("A-Counter: " + counter);
V(mutex);
}
private void do_something_else() {
// unkritischer Abschnitt
System.out.println("Thread_A: wieder unkritisch");
}
}
public static class Thread_B extends Thread {
public void run() {
System.out.println("Thread_B ist gestartet.");
P(mutex);
counter = 20;
counter++;
counter++;
counter++;
counter++;
counter++;
counter++;
System.out.println("B-Counter: " + counter);
V(mutex);
}
}
public static void main(String[] args) {
Thread a = new Thread_A();
Thread b = new Thread_B();
a.start();
b.start();
}
}
Entscheidend sind hier die Zeilen 4, 19, 26, 38 und 49.
In Zeile 4 wird zunächst eine Variable mit dem Namen mutex vom Datentyp Semaphore deklariert und initialisiert. Hier wird also ein binärer Semaphor (Mutex) erzeugt.
Der kritische Abschnitt von Thread_A liegt in den Zeilen 21 bis 24.
Direkt davor (in Zeile 19) wird die Methode P(mutex); aufgerufen, direkt danach (in Zeile 26) wird die Methode V(mutex); aufgerufen.
Der kritische Abschnitt von Thread_B liegt in den Zeilen 40 bis 47.
Direkt davor (in Zeile 38) wird die Methode P(mutex); aufgerufen, direkt danach (in Zeile 49) wird die Methode V(mutex); aufgerufen.
Der hier abgedruckte Quellcode zu Listing 2 ist in dieser Form nicht ausführbar. Er soll lediglich beispielhaft verdeutlichen, an welchen Stellen zusätzliche Operationen im Bezug auf den Mutex zur Realisierung des wechselseitigen Ausschlusses eingefügt werden müssen.
Zwar gibt es in Java eine Klasse Semaphore, siehe
http://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Semaphore.html
jedoch sind hier die P() und V()-Operationen anders benannt:
acquire() bzw. release()
Unter dem hier verlinkten Dokument findet sich bei Bedarf ein anderes sehr einfaches Java-Beispiel, welches den praktischen Einsatz der Semaphore-Klasse zeigt.
Spiele einmal gedanklich den Ablauf der beiden Threads aus Listing 2 durch.
Was passiert, wenn Thread_A bereits seinen kritischen Abschnitt betreten hat, und beispielsweise in Zeile 23 ein Kontextwechsel zu Thread_B erfolgt?
Vergleiche den obigen Quellcode aus Listing 2 (Mutex) einmal mit dem bereits bekannten Quellcode mit dem Beispiel des Aktiven Wartens (mit while).
public class Beispiel_Aktives_Warten_mit_while {
static int counter = 0; // gemeinsam genutztes Betriebsmittel
static int lock = 0; // Sperrvariable
public static class Thread_A extends Thread {
public void run() {
do_something(); // unkritisch
count_from_10(); // kritisch !!!
do_something_else(); // unkritisch
}
private void do_something() {
// unkritischer Abschnitt
System.out.println("Thread_A: unkritisch");
}
private void count_from_10() {
// Vorsicht: kritischer Abschnitt!
while (lock == 1); // Semikolon beachten!
lock = 1;
counter = 10;
counter++;
counter++;
System.out.println("A-Counter: " + counter);
lock = 0;
}
private void do_something_else() {
// unkritischer Abschnitt
System.out.println("Thread_A: wieder unkritisch");
}
}
public static class Thread_B extends Thread {
public void run() {
System.out.println("Thread_B ist gestartet.");
while (lock == 1); // Semikolon beachten!
lock = 1;
counter = 20;
counter++;
counter++;
counter++;
counter++;
counter++;
counter++;
System.out.println("B-Counter: " + counter);
lock = 0;
}
}
public static void main(String[] args) {
Thread a = new Thread_A();
Thread b = new Thread_B();
a.start();
b.start();
}
}
Hier ist ein Bild mit beiden Quelltexten nebeneinander:
Die in Listing 2 beispielhaft gezeigte Verwendung eines Mutex bezieht sich im Quellcode auf zwei Threads. Ein Mutex wird in Betriebssystemen aber üblicherweise eingesetzt, um die kritischen Abschnitte zweier Prozesse (mit einem gemeinsam genutzten Betriebsmittel) gegeneinander abzuschotten.
Wikipedia zeigt auf der Seite
http://de.wikipedia.org/wiki/Semaphor_%28Informatik%29#Anwendungsbeispiele
im Abschnitt "Einsatz in Konkurrenzsituationen I" ein sehr einfach gehaltenes Beispiel mit zwei Prozessen und einem Mutex.
Schau' es dir dort an!
Das Erzeugen eines Mutex sowie die P()- und V()-Operation kann man sich bei Betriebssystemen als Systemaufrufe vorstellen. Somit verwaltet das Betriebssystem die Warteschlange eines Semaphors und kann problemlos dafür sorgen, dass die P()- und V()-Operation atomar abläuft. Das Betriebssystem kann dann mit dem Scheduler die wartenden Prozesse in den Status "wartend" versetzen - was nicht möglich wäre, wenn die Programmiersprache selbst die Mutex-Verwaltung durchführen würde.
An der FH Köln wird ein Semaphor Workshop mit Java-Applets bereitgestellt, anhand derer der wechselseitige Ausschluss mit Hilfe eines binären Semaphors (Mutex) nachvollzogen werden kann. Probiere es aus!
http://www.nt.fh-koeln.de/fachgebiete/inf/diplom/semwork/beispiele/waus/waus.html
Falls das Java-Applet in deinem Browser nicht startet, musst du eventuell die Java-Sicherheitseinstellungen anpassen. Trage dort ein: http://www.nt.fh-koeln.de
Diese Seite steht unter der Creative Commons Namensnennung 3.0 Unported Lizenz http://i.creativecommons.org/l/by/3.0/80x15.png