Betriebssysteme Wintersemester 2015 Kapitel 2 – Prozess und Threads Patrick Kendzo [email protected] Programm Inhalt Einleitung Prozesse und Threads Speicherverwaltung Eingabe und Ausgabe Dateisysteme Zusammenfassung + Klausurvorbereitung 2 Betriebssysteme Wintersemester 2015 © Patrick Kendzo Wiederholung Synchronization von JavaThreads (1) ☞ Einsatz vom Synchronized-Mechanismus zur Synchronisation paralleler Threads unzureichend Beispielszenario: Parkhaus Ein Parkhaus besteht aus N Parkplätzen Einfahr nur möglich, falls weniger als N Autos im Parkhaus sind, sonst muss man warten Fährt ein Auto aus, kann ein evt. wartendes Auto einfahren Umsetzung in Java: (1) (2) (3) (4) (5) 3 Klasse mit Attribut für die Anzahl freier Plätze initialisiert mit N Methoden zum Herunter- und Heraufzählen (für Einfahren bzw. Ausfahren) des Attributs N Da N von mehreren Threads (Autos) benutzt wird, müssen diese Methoden synchronized sein Beachte: Falls N = 0 ist, kann es nicht heruntergezählt werden (es kann nicht weniger als 0 freie Plätze geben), sondern das Auto muss warten (möglichst nicht mit aktivem Warten realisiert) Falls das Attribut N wieder hochgezählt wird, muss ein evtl. wartender Thread benachrichtigt werden, dass jetzt eine Chance besteht, das Attribut herunterzuzählen Betriebssysteme Wintersemester 2015 © Patrick Kendzo Wiederholung Synchronization von JavaThreads (2) Beispielszenario: Parkhaus (continued) Lösungsanalyse Versuch 3: Nutzung von wait() und notify() wait() und notify() sind Methode der Klasse Objekt (werden daher von jeder Klasse geerbt) public final void wait() throws InterruptedException; public final void notify(); Beachte: wait() und notify() sind nur aus synchronized-Methoden aus aufrufbar! Sie müssen auf ein Objekt angewendet werden, das in diesem Augenblick durch synchronized gesperrt ist Sonst: Ausnahme IllegalMonitorStateException 4 Betriebssysteme Wintersemester 2015 © Patrick Kendzo Wiederholung Synchronization von JavaThreads (3) Beispielszenario: Parkhaus (continued) Lösungsanalyse Versuch 3: Nutzung von wait() und notify() (continued) Folge: (1) Bei wait() wird Sperre auf das Objekt aufgegeben und der Thread wird blockiert (2) notify() ermöglicht irgendeinem an diesem Objekt wartenden Thread weiterzulaufen (3) Falls momentan kein Thread an diesem Objekt wartet, so hat der Aufruf von notify keine Wirkung (4) Falls ein durch wait() wartender Thread weiterlaufen kann, weil er von notify ausgewählt wurde, so muss er das Objekt vor dem Weiterlaufen erneut sperren ☞ Alternative wait()-Varianten public final void wait(long timeout) throws InterruptedException; public final void wait(long timeout, int nanos) throws InterruptedException; 5 Betriebssysteme Wintersemester 2015 © Patrick Kendzo Wiederholung Synchronization von JavaThreads (4) Erzeuger-Verbraucher-Problem Grenzen von wait() und notify() Beispielszenario: Modellierung einer Datenübergabe Ein Thread (Produzent, Erzeuger) legt Daten (z.B. Zahl) in einem Puffer ab ( z.B. ein neuer Messwert - Temperatur, Geschwindigkeit usw.) Ein anderer Thread (Konsument, Verbraucher) holt Daten (z.B. Zahl) aus einem Puffer heraus (zB. zum anzeigen, Mittelwertberechnung, Speichern) Ziele: Jeder Messwert soll nur einmal angezeigt (bzw. gelesen) werden Es soll kein Messwert verloren gehen Es soll aktives Warten vermieden werden Versuch 1: Realisierung mit synchronized – wait - notify Beobachtung: Falls genau ein Produzent und ein Verbraucher vorhanden sind, dann funktioniert eine Lösung mit synchronized , wait() und notify() Falls mehrere Produzenten und Konsumenten vorhanden sind, so bleiben diese nach einer gewissen Zeit (nichtdeterministisch) stehen Lösung: Verwendung von notifyAll() Alle Threads , die auf die Freigabe der Sperre auf ein Objekt warten werden geweckt... 6 Betriebssysteme Wintersemester 2015 © Patrick Kendzo Synchronization von Java-Threads (1) Erzeuger-Verbraucher-Problem (continued) Mit wait und notifyAll 7 class Buffer { private boolean available = false; private int data; public synchronized void put(int x) { while(available) { try{ wait(); }catch(InterruptedException e) {} } data = x; available = true; notifyAll(); } public synchronized int get(){ while(!available) { try{ wait(); }catch(InterruptedException e) {} } available = false; notifyAll(); return data; } } Betriebssysteme Wintersemester 2015 © Patrick Kendzo Synchronization von Java-Threads (2) Erzeuger-Verbraucher-Problem (continued) Mit wait und notifyAll class Producer extends Thread{ private Buffer buffer; private int start; public Producer(Buffer b, int s){ buffer = b; start = s; } public void run(){ for(int i = start; i < start + 100; i++){ buffer.put(i); } } } 8 class Consumer extends Thread{ private Buffer buffer; public Consumer(Buffer b){ buffer = b; } public void run(){ for(int i = 0; i < 100; i++){ int x = buffer.get(); System.out.println("got " + x); } } } Betriebssysteme Wintersemester 2015 © Patrick Kendzo Synchronization von Java-Threads (3) Erzeuger-Verbraucher-Problem (continued) Mit wait und notifyAll public class ProducerConsumer{ public static void main(String[] args){ Buffer b = new Buffer(); Consumer c1 = new Consumer(b); Consumer c2 = new Consumer(b); Consumer c3 = new Consumer(b); Producer p1 = new Producer(b, 1); Producer p2 = new Producer(b, 101); Producer p3 = new Producer(b, 201); c1.start(); c2.start(); c3.start(); p1.start(); p2.start(); p3.start(); } } 9 Betriebssysteme Wintersemester 2015 Ausgabe: ... got 98 ... got 97 ... © Patrick Kendzo Synchronization von Java-Threads (4) Nutzung von notifyAll notifyAll() ist eine Methode der Klasse Objekt (wie wait() und notify()) public final void notifyAll(); Beachte: (1) Genau wie bei wait() und notify() ist notifyAll() nur aus synchronized-Methoden aufrufbar! (2) notifyAll() kann immer statt notify() verwendet werden Korrektheit ist nicht betroffen, aber Effizienz notifyAll() muss in folgenden Fällen statt notify() verwendet werden: (3) a) Es können mehrere wartende Threads existieren, die wegen unterschiedlicher Bedingungen warten (z.B.: Erzeuger-Verbraucher-Problem) b) Durch die Zustandsänderung eines Objekts können die Wartebedingung mehrerer wartenden Threads hinfällig werden (z.B.: Verkehrsampel) 10 Betriebssysteme Wintersemester 2015 © Patrick Kendzo Synchronization von Java-Threads (5) Nutzung von notifyAll (continued) Wirkung: notifyAll() ermöglicht allen an diesem Objekt wartenden Threads weiterzulaufen (Aber nicht allen in einem Prozess vorhandenen Threads!) Falls momentan kein Thread an diesem Objekt wartet, so ist der Aufruf von notifyAll() wirkungslos 11 Betriebssysteme Wintersemester 2015 © Patrick Kendzo Prozesszutänden (1) ☞ Ein Prozess kann sich in den Zuständen rechnend, rechenbereit und blockiert befinden Rechnend 1 2 3 Blockiert Rechenbereit 1. Prozess blockiert, weil er z.B. auf EA wartet 2. Scheduler wählt einen anderen Prozess aus 3. Scheduler wählt diesen Prozess aus 4. Eingaben vorhanden 4 Quelle: vgl.Tanenbaum 12 Betriebssysteme Wintersemester 2015 © Patrick Kendzo Prozesszutänden (2) ☞ Zustandsübergangsdiagramm für Java-Threads Quelle: vgl. Oechsle 13 Betriebssysteme Wintersemester 2015 © Patrick Kendzo Klassische Probleme der Interprozesskomunikation (1) ☞ Synchronisationsmechanismen von Java (synchronized, wait, notify, notifyAll) werden an Beispielen angewendet Hintergrund: - Bis jetzt Synchronisation von Threads innerhalb eines Prozesses. Mit Zugriff auf gemeinsame Objekte - - - Thread teilen sich den selben Adressraum (Metapher: Köche in einer gemeinsamen Küche) Synchronisations- und Kommunikationsmechanismen in gängiger BS - Finden vorwiegen zwischen Prozessen statt - Prozesse besitzen keine gemeinsamen Adressraum Die Funktionalität der Konzepte werden in einer objektorientierten Weise (Java) nachgeahmt „klassische“ Konzeptbeispiele: (sind alle dem BS UNIX entlehnt) Semaphore (klassische Synchronisation) Message Queues (klassische Kommunikation) Pipes (klassische Kommunikation) 14 Betriebssysteme Wintersemester 2015 © Patrick Kendzo Klassische Probleme der Interprozesskomunikation (2) ☞ Synchronisationsmechanismen von Java (synchronized, wait, notify, notifyAll) werden an Beispielen angewendet (continued) „klassische“ Synchronisationsaufgaben: Werden in Lehrbüchern zur Illustration von Synchronisationskonzepten herangezogen Beispiele: Philosophen-Problem Leser-Schreiber-Problem 15 Betriebssysteme Wintersemester 2015 © Patrick Kendzo Klassische Probleme der Interprozesskomunikation (3) ☞ Einfache Semaphore Klassische Form der Synchronisation in Betriebssystemen (schon als Parkhaus bekannt) public class Semaphore{ private int value; public Semaphore(int init){ if(init < 0) init = 0; value = init; } public synchronized void p(){ while(value == 0){ try{ wait(); }catch(InterruptedException e) {} } value--; } public synchronized void v(){ value++; notify(); } } 16 Bisher Parkhaus Bisher plätze Betriebssysteme Wintersemester 2015 Bisher passieren() / enter() Bisher verlassen() / leave() © Patrick Kendzo Klassische Probleme der Interprozesskomunikation (4) ☞ Einfache Semaphore (continued) Anwendungsbereiche: Gegenseitiger Ausschluss (Mutual Exclusion) Bedeutung: zu einem Zeitpunkt t kann ein bestimmtes Programmstück (kritischer Abschnitt) von höchstens einem Thread ausgeführt werden (snychronized in Java) In Kontext von Threads aus unterschiedlichen Prozessen stellt den Betriebssystemkern ein Semaphor (Mutex-Semaphore) zur Verfügung, die als Parameter eines Systemaufrufs mit übergeben wird. Herstellung vorgegebener Ausführungsreihenfolgen von Aktionen in unterschiedlichen Threads a2 a1 a3 a5 a4 17 Betriebssysteme Wintersemester 2015 Zeitliche Relation zwischen den Aktionen: a1 vor a2 a2 und a3 parallel ... © Patrick Kendzo Klassische Probleme der Interprozesskomunikation (5) ☞ Additive Semaphore Verallgemeinerung von einfachen Semaphoren Bis jetzt wurde der Semaphorenwert nur um eins erhöht oder erniedrigt Mit Additiver Semaphore kann dieser um beliebige Werte erhöht oder erniedrigt werden 18 Betriebssysteme Wintersemester 2015 © Patrick Kendzo Klassische Probleme der Interprozesskomunikation (6) ☞ Additive Semaphore (continued) public class AdditiveSemaphore{ private int value; public AdditiveSemaphore(int init){ if(init < 0) init = 0; this.value = init; } public synchronized void p(int x){ if(x <= 0) // x muss positiv sein! Warum? return; while(value - x < 0){ try{ wait(); }catch(InterruptedException e) {} } value -= x; } public synchronized void v(int x){ if(x <= 0) // x muss positiv sein! Warum? return; value += x; notifyAll(); // nicht notify } 19 Betriebssysteme Wintersemester 2015 public void p(){ // wie bisher p(1); } public void v(){// wie bisher v(1); } public void change(int x){ if(x > 0) v(x); // erhöht um x else if(x < 0) p(-x); // erniedrigt um x } } © Patrick Kendzo Klassische Probleme der Interprozesskomunikation (7) ☞ Additive Semaphore (continued) Schrittweises Erniedrigen vs. Erniedrigen „auf einen Schlag“ Der Aufruf „p(3)“ ist nicht gleich 3 aufeinander folgende Aufrufe „p(1)“ bzw. „p()“ Beispiel 20 Gegeben: Additiver Semaphor „S“ mit Wert „4“ und zwei Threads („T1“ und „T2“), die gemeinsam S benutzen Annahme: S soll von T1 und T2 jeweils um 3 erniedrigt werden Schrittweises Erniedrigen ( Aufruffolge von p(1)) kann zu Verklemmung führen! Beim Erniedrigen auf einen Schlag entsteht keine Verklemmung. Ein Thread kann erniedrigen, der Andere muss warten Betriebssysteme Wintersemester 2015 © Patrick Kendzo Klassische Probleme der Interprozesskomunikation (8) ☞ Semaphorgruppen Verallgemeinerung der Additiven Semaphoren In UNIX werden diese Gruppen einfach Semaphore genannt Mit Aufruf der change-Methode können mehrere Semaphore der Gruppe „auf einen Schlag“ verändert werden Änderungen werden dann nur durchgeführt, wenn nach Änderung alle Semaphore-Werte der Gruppe nicht negativ sind Sonst wird gewartet (Alles oder Nichts!) 21 Betriebssysteme Wintersemester 2015 © Patrick Kendzo Klassische Probleme der Interprozesskomunikation (9) ☞ Semaphorgruppen public class SemaphoreGroup{ private int[] values; public SemaphoreGroup(int numberOfMembers){ if(numberOfMembers <= 0) return; values = new int[numberOfMembers]; } public int getNumberOfMembers(){ return values.length; } public synchronized void changeValues(int[] deltas){ if(deltas.length != values.length) return; while(!canChange(deltas)){ try{ wait(); }catch(InterruptedException e){} } doChange(deltas); notifyAll(); } 22 private boolean canChange(int[] deltas){ for(int i = 0; i < values.length; i++) if(values[i] + deltas[i] < 0) return false; return true; } private void doChange(int[] deltas){ for(int i = 0; i < values.length; i++) values[i] = values[i] + deltas[i]; } } Betriebssysteme Wintersemester 2015 © Patrick Kendzo