Architektur Verteilter Systeme Teil 6: Interprozess-Kommunikation 09.05.15 1
Literatur [6-1] http://php.net/manual/de/book.sockets.php [6-2] http://de.wikipedia.org/wiki/socket_(software) [6-3] http://php.net/manual/de/book.network.php 2
Übersicht Ein paar Begriffe Shared Memory Pipelines Messages Ports Sockets 3
Grundbegriffe Interprocess-Kommunikation = Austausch von Daten über einen Kanal zwischen Prozessen bzw. Threads innerhalb eines Systems Ein Kanal realisiert in der Regel einen Datenstrom in einer Richtung, so dass für eine bidirektionale Verbindung zwei Kanäle erforderlich sind. Schema: T 1 T 2 write() read() T 1 T 2 D 4 D 3 D 2 D 1 Queue 4
Einteilung I Interprozess-Kommunikation speicherbasiert strombasiert nachrichtenbasiert Shared Memory Pipe Named Pipe Sockets Messages Signale Exceptions 5
Einteilung II Anzahl der Teilnehmer Unicast Multicast Richtung Unidirektional Bidirektional Entfernung der Teilnehmer Im selben Virtuellen Speicher Im selben System Zwischen Systemen 6
Shared Memory I Beide Kommunikationspartner befinden sich im selben RAM. Unterscheidung zwischen: (a) Zwei Threads innerhalb eines Prozesses (b) Zwei Prozesse VM = Virtueller per MMU realisierter Adressraum Thread1 Thread2 Prozess1 VM VM Prozess2 Prozess VM 7
Shared Memory II mit MMU Prozess1 Prozess2 Code Heap Stack Code Heap Stack Virtueller Speicher Abbildung durch MMU Realer Speicher 8
Bemerkungen Die Segmente der Prozesse werden in gleichgroße Seiten (Pages) meist 4 KByte lang aufgeteilt. Die MMU bildet diese Seiten in beliebiger Anordnung in den RAM ab. Eine Seite wird gemeinsam auf denselben Bereich abgebildet; das ist der gemeinsam benutzte Speicher (Shared RAM). 9
Producer/Consumer mit Monitoren I Producer Consumer Zirkulärer Puffer Die Producer/Consumer-Mechanismen gehören zu den Standard-Verfahren der Kommunikation zwischen Threads bzw. Prozessen. Producer: In einen freien Bereich werden Daten geschrieben. Dann werden diese vom Producer für den Consumer frei gegeben. Consumer: Die vom Producer geschriebenen Daten werden entnommen. Der nun ausgelesene Bereich wird für den Producer frei gegeben. 10
Idee des Zirkulären Puffers I Producer: Der Index in zeigt auf das nächste zu beschreibende Element. Der Producer schreibt dort und erhöht modulo der Größe in. Consumer: Der Index out zeigt auf das nächste zu lesende Element. Der Consumer liest dort und erhöht modulo der Größe out. 11
Idee des Zirkulären Puffers II MOD ist der Modulo-Operator, dessen Anwendung dafür sorgt, dass die beiden Indices in und out sich immer innerhalb der erlaubten Array-Grenzen bewegen. Es gibt folgende Bedingungen: Der Puffer ist dann leer, wenn gilt: in = out Der Puffer ist dann voll, wenn gilt: in+1 MOD size = out Wenn beide Bedingungen nicht gelten ist der Puffer gefüllt. Um die Implementierung zu vereinfachen, wird in der Variablen count die Anzahl der belegten Elemente vermerkt. Statt eines Array mit den langsamen Indizierungen wird häufig mit Pointern in ein Array gearbeitet. 12
Producer/Consumer mit Monitoren II Teil 1 MONITOR Buffer { elem[0..size-1] Buffer; INT count; INT in, out; CONDITION NonEmpty, NonFull; write(elem what) { IF count = size THEN waitcond(nonfull); FI Buffer[in]:= what; in:= (in+1) MOD size; count++; signalcond(nonempty); } 13
Producer/Consumer mit Monitoren III Teil 2 elem read(){ } IF count = 0 THEN waitcond(nonempty); FI what:= Buffer[out]; out:= (out+1) MOD size; count--; signalcond(nonfull); return(what); } /* Initialisierung */ count:= in:= out:= 0; Dies ist eine typische Ring-Puffer-Lösung zur Interprozesskommunikation. 14
Bemerkungen I Es gibt zwei Bedingungsvariablen: NonEmpty symbolisiert die Bedingung, dass der Puffer nicht leer ist, d.h. es ist irgendetwas enthalten NonFull symbolisiert die Bedingung, dass der Puffer nicht vollständig gefüllt ist, d.h. es gibt noch Platz Wenn nun ein Thread etwas in den Puffer schreiben will, muss vorher Platz da sein. Ist der Puffer voll, legt er sich auf das Ereignis, dass mindestens für ein Element Platz ist, schlafen: waitcond(nonfull) Wenn ein Thread etwas aus dem Puffer lesen will, muss mindestens ein geschriebener Eintrag vorhanden sein. Wenn nicht, so legt er sich auf das Ereignis schlafen, dass etwas in den Puffer geschrieben wurde: waitcond(nonempty) Die entsprechenden Threads, die lesen bzw. schreiben, wecken die wartenden Threads auf: signalcond(nonfull) und signalcond(nonempty) 15
Bemerkungen II Monitore werden mit den vorgestellten Mitteln: p(), v() etc. implementiert. Regeln Kein Thread sollte sich lange in einem Monitor aufhalten. Ein Ausschluss sollte nicht zu lange erfolgen. Schachteln von Monitoren kann zu Deadlocks führen. Threads, die sich in geschachtelten Monitore befinden, sollten höher priorisiert werden, damit sie schneller die Monitore frei geben. 16
Wo gibt es Monitore? In fast allen Sprachen gibt es keine Monitore; sie müssen dort zu Fuß programmiert werden was ihren Sinn in Frage stellt. Ausnahmen: Concurrent Pascal Siehe: http://authors.library.caltech.edu/34677/1/06312840.pdf http://brinch-hansen.net/papers/1993a.pdf Modula2 (in Abwandlung) Mesa Java (in Abwandlung) Pascal-FC Normalerweise werden Producer/Consumer-Probleme mit Semaphoren realisiert. 17
Dasselbe noch einmal ohne Monitore I CLASS Buffer { elem[0..size-1] Buffer; INT in, out; Semaphore mutex:= 1; Semaphore empty:= size; Semaphore full:= 0; KA write(elem what) { p(empty); p(mutex); Buffer[in]:= what; in:= (in+1) MOD size; v(mutex); v(full); } KA } KA= kritischer Abschnitt elem read(){ p(full); p(mutex); what:= Buffer[out]; out:= (out+1) MOD size; v(mutex); v(empty); return(what); } /* Initialisierung */ in:= out:= 0; 18
Erklärungen Hier werden Integer-Semaphoren verwendet. Die initiale Zahl gibt die Anzahl der Threads an, die den kritischen Abschnitt betreten, bis sie schlafen gelegt werden. Bisher hatten wir binäre Semaphoren mit dem initialen Wert von 1. Die eine Semaphore (empty) zählt die freien Bereiche, die andere die belegten Bereiche. Die dritte Semaphore (mutex = mutual exclusion) ist eine bool'sche Semaphore und erlaubt nur einem Thread die Manipulation der Queue. 19
Bewertung Shared Memory Standard-Methode bei der Kommunikation zwischen Threads Sehr effizient (kein Kopieren) Synchronisation liegt in der Hand der Programmier(innen) Quelle vieler Fehler Kein/Kaum Unterstützung durch Compiler/Programmiersprachen Ein falscher Pointer: Tod für alle Threads 20
Zusammenfassung des Bisherigen T 1 T 2 write() read() Abstraktion Queue D 4 D 3 D 2 D 1 Die Realisierung wird in einem Abstrakten Datentyp, z.b. einer Klasse, versteckt. Die Queue liegt aber immer noch innerhalb des Virtuellen Speichers der Beteiligten. 21
Über das Betriebssystem I T 1 T 2 Prozesse/Threads pipe() write() read() Manager create() destroy() D 4 Queue D 3 D 2 D 1 Betriebssystem Derartige Queues werden Pipes (Pipelines) genannt. In Unix werden diese im Shell mit einem senkrechten Strich ausgedrückt, z.b. ls more. 22
Über das Betriebssystem II Der pipe-syscall erzeugt eine Queue, die mit dem Schließen (close) wieder zerstört wird. In die Queue wird mit write() geschrieben, aus ihr mit read() gelesen. Beide Operationen sind wartend, in dem Sinne, dass beim read() gewartet wird bis etwas in der Pipe ist, beim write() gewartet wird bis Platz in der Pipe ist. Über Pipes wird üblicherweise zwischen Prozessen kommuniziert, es geht aber auch zwischen Threads eines Prozesses. 23
Pipelines über das Dateisystem I T 1 T 2 Prozesse/Threads Open() Write write() read() Open() Read connect() Manager create() destroy() Queue Queue Queue Betriebssystem Im Dateisystem werden die Queues angelegt und im Kernel realisiert: named pipe genannt 24
Pipelines über das Dateisystem II Die Enden einer Pipe haben nun einen Namen, der in die Namen des Dateisystem integriert ist. Damit ist die Existenz einer Pipe unabhängig von der Ausführung eines Prozesses. Pipes liegen im RAM und werden mit Terminieren bzw. Schließen der Verbindung zerstört. Named Pipes werden im Dateisystem angelegt und erst dann zerstört, wenn sie gelöscht werden. Damit realisieren sie allgemeine Schnittstellen zu Prozessen. 25
Pipelines über das Dateisystem III Client 1 Client 2 write() write() read() Server 1 Client N write() Dateiname pipe Server werden über den Namen einer Queue angesprochen. Aber: die Verbindung zwischen Queue (Pipe) und Server ist lose, d.h. es können ein oder mehrere Server an den Pipe-Ausgang anschließen. Der (Datei-)Name der Pipe ist daher kein Service-Name. 26
Feste Verbindung: Port I Client 1 Server 1 Client 2 send() receive() send() Client N send() Portname Port Queue Wenn eine feste Verbindung zwischen Schnittstelle und Server als Schnittstelle zum Server besteht, wird von Ports gesprochen. Das Schreiben in ein Port wird send() und das Lesen receive() genannt. Die ausgetauschten Daten werden Messages (Nachrichten) genannt. 27
Feste Verbindung: Port II Dies ist die höchste Abstraktion von der Kommunikationstechnik. Für die Prozesse/Threads ist nicht erkennbar, ob der Partner im selben System oder in einem anderen System liegt. Unter Unix gibt es das Message-Konzept für Kommunikation innerhalb eines Systems in den Varianten der synchronen und asynchronen Kommunikation gibt es Socket-Konzept für Kommunikation zwischen Systemen Dies geht auch als Spezialfall innerhalb eines Systems 28
Sockets Socket = Sockel = Begriff aus dem UNIX-Kernel für Schnittstellen zu anderen Prozessen auf demselben oder fremden Systemen (über ein Netz) Client 1 Server 1 send() receive() Socket TCP/IP-Verbindung Socket Betriebssystem Betriebssystem 29
Bewertung Nachrichtenbasierte Kommunikation Standardverfahren bei Kommunikation zwischen Systemen; Alternative Sockets mit offenen Strom Hoher Abstraktionsgrad Passt ins Konzept von Objekt-Orientierten Sprachen wird aber von denen nicht/kaum unterstützt Ineffizient, da fast immer mit Kopieren gearbeitet wird wenn innerhalb des Systems kommuniziert wird Das Beste ist wohl eine nachrichtenbasierte Kommunikation mit Hilfe von Shared Memory, da bei hoher Abstraktion eine hohe Effizienz realisiert wird. 30
Synchron und Asynchron I send() Synchron: der Sender wartet solange, bis der Empfänger empfangen hat Asynchron: der Sender setzt seine Nachricht ab und arbeitet weiter; diese wird in einer Queue zwischen gespeichert receive() Synchron: der Empfänger wartet solange, bis eine Nachricht für ihn vorhanden ist Asynchron: der Empfänger empfängt eine Nachricht, falls sie da ist, ansonsten arbeitet er weiter. Es sind nun alle vier Kombinationen möglich. Bisher wurden die asynchronen vorgestellt. Bei diesen ist eine Queue erforderlich. Aber: Läuft die Queue über geht beim send() die Nachricht verloren oder das send() geht in den Asynchronen Modus über 31
Synchron und Asynchron II Synchrone Operationen (blockierend) Direkte Kommunikation Keine Zwischenspeicherung der Nachricht falls innerhalb eines Systems Implizite Empfangsbestätigung Einfach und schnell Asynchrone Operationen (nicht blockierend) Queue erforderlich Keine Deadlocks Wenn Sender terminiert, entsteht beim Empfänger kein Deadlock Empfangsbestätigung kompliziert 32
Synchron und Asynchron III In der Praxis: Wenn send() und receive() blockieren, wird auch vom Rendezvous gesprochen. Meistens ist send() nicht blockierend (no-wait-send), während receive() blockierend ist. 33
Nach dieser Anstrengung etwas Entspannung... Schnee, nichts als Schnee soweit das Auge reicht 34