10. Parallele Prozesse | |||||||||||
|
|||||||||||
10.8 Datenaustausch zwischen Prozessen ohne Rendezvous
Mit geschützten Typen oder gemeinsamen Variablen ("shared variables") können zwei oder mehr Prozesse Daten austauschen, ohne sich zu einem Rendezvous zu synchronisieren. Gemeinsame Variable sind globale Objekte für zwei oder mehr Prozesse. Wird eine gemeinsame Variable von jedem Prozeß nur gelesen, so ist dies ohne Einschränkung möglich. In einem solchen Fall sollte die Variable allerdings als Konstante deklariert werden. Wird allerdings sowohl lesend wie schreibend auf die gemeinsame Variable zugegriffen, so ergeben sich verschiedene Fehlermöglichkeiten. Beispiel 1: Es wird ein Prozeß explizit und ein Prozeß implizit deklariert. Beide haben eine gemeinsame Variable, wobei der eine Prozeß Werte in die gemeinsame Variable hineinschreibt und der andere Prozeß diese Werte liest. Die gemeinsame Variable ist nicht geschützt.
Die beiden Prozesse benutzen die Verzögerungsanweisung ("delay"), um die Prozessorkapazität an den jeweils anderen abzugeben. Damit wird ermöglicht, daß beide Prozesse die gemeinsame Variable "gemeinsame_variable" abwechselnd beschreiben und lesen können. Es gibt verschiedene Fehlerquellen:
Mehrere Prozesse steuern über die "gemeinsame_variable" den Zugriff auf eine bestimmte Ressource, die immer nur von einem Prozeß zur gleichen Zeit benutzt werden darf.
Dann ist folgender Fehler möglich: Mehr als ein Prozeß wertet "gemeinsame_variable" (in verschiedenen aber ähnlichen If-Anweisungen wie bei (A)) aus, bevor "gemeinsame_variable" auf "false" gesetzt wird (B). Dann manipuliert mehr als ein Prozeß die Ressource. Ist die Ressource z. B. ein Drucker, so kann es zu unerwünschten Effekten kommen. Beide Fehlermöglichkeiten aus Beispiel 1 können mittels des Pragmas "atomic" vermieden werden. Durch Ersetzen der Kommentarzeile "-- (X)" im Beispiel 1 mit
wird dem Übersetzer mitgeteilt, daß die "gemeinsame_variable" von mehreren Prozessen gleichzeitig angesprochen wird. Damit legt der Übersetzer keine lokalen Kopien an. Zu dem zweiten Fehler (lesender Zugriff auf die Variable, während diese noch verändert wird) muß zuvor noch der Begriff "unteilbare Operation" eingeführt werden. Eine unteilbare Operation ist eine Operation, die immer beendet wird, bevor ein Prozeßwechsel aktiviert wird. Ist die Variable von einem skalaren Typ, so ist das Lesen wie auch das Schreiben eine unteilbare Operation. Ist die Variable dagegen ein strukturierter Typ, so gilt das nicht mehr uneingeschränkt. Bei einem strukturierten Typ kann das Lesen oder Schreiben je nach Umfang (Anzahl zu transferierender Zeichen) in mehrere (unteilbare) Operationen zerfallen. Das Pragma "atomic" weist den Übersetzer für Verbunde an, Lesen und Schreiben so zu übersetzen, daß erst nach Beendigung des Lesens das Schreiben zugelassen wird und umgekehrt. Beide Operationen werden sequentialisiert. Die geschützten Typen wurden eingeführt, weil die Benutzung von Prozessen oft ein zu mächtiges Mittel darstellt. Der Einsatz von Prozessen zur Kapselung einer Vielzahl einfacher Datenaustausche wirkt sich sowohl auf die Übersichtlichkeit als auch auf das Laufzeitverhalten negativ aus. Soll mehr als ein Prozeß auf Daten lesend und schreibend zugreifen, die immer nur von genau einem Prozeß zu einem Zeitpunkt in Besitz genommen werden dürfen, so sind diese Zugriffe zu sequentialisieren, indem die Daten in einem Prozeß gekapselt werden. Dann muß sich jeder Prozeß, der Zugriff haben will, mit dem Kapselprozeß synchronisieren, der bis auf die Rendezvous passiv ist. Um den Aufwand für Synchronisationen mit solchermaßen passiven Prozessen zu vermindern, die nur benutzt werden, um Zugriffe zu sequentialisieren, gibt es die geschützten Typen. Geschützte Typen (protected_type_declaration <BNF>) ermöglichen den sequentialisierten Zugriff von mehreren Prozessen auf gemeinsam genutzte Datenbereiche, die nur von einem Prozeß zu einem Zeitpunkt genutzt werden dürfen, ohne daß eine Synchronisation erforderlich wird. Obwohl geschützte Typen keine Prozesse sind, haben sie einige Eigenschaften dieser. Geschützte Typen müssen wie Prozesse eine Spezifikation und einen Rumpf (Geschützter Rumpf) haben. Der Rumpf darf außer den Rümpfen der in der Spezifikation aufgeführten Unterprogramme und Eingänge nur noch lokale Unterprogramme enthalten. Lokale Typen bzw. Variablen müssen in der Spezifikation nach dem Schlüsselwort "private" vereinbart werden. Eine (geschützte) Aktion bezeichnet entweder einen Eingangsaufruf oder einen Unterprogrammaufruf eines "geschützten Typs". Eine neue (geschützte) Aktion liegt immer dann vor, wenn diese ein externer Aufruf ist. Liegt ein interner Aufruf vor, so ist dieser auch keine neue Aktion. Das bedeutet, daß eine neue Aktion nicht gestartet wird, solange eine andere Aktion für dasselbe Objekt gerade ausgeführt wird. Wird aus dem Rumpf eines geschützten Typs ein Eingang oder ein Unterprogramm (implizit) für dieselbe Instanz aufgerufen, so ist das keine neue Aktion. Ein expliziter Aufruf für dieselbe Instanz ist natürlich ein externer Aufruf und damit eine neue Aktion. Ein geschützter Typ ist also gegen neue Aktionen gesperrt, solange ein Unterprogrammaufruf oder ein Eingangsaufruf abgearbeitet wird, auch wenn dieser mehrere interne Aufrufe zur Folge hat. Für (geschützte) Aktionen gibt es ein analoges Verhalten wie bei Prozessen zwischen den Zuständen "fertig ausgeführt" und "beendet". Wenn eine Aktion alle Anweisungen durchlaufen hat, wird sie nicht sofort beendet, sondern das geschützte Objekt wird als Ganzes für den nächsten Zugriff aufbereitet (siehe das folgende Kapitel geschützte Eingänge). Während dieser Zeit ist das Objekt weiterhin gegen neue Aktionen gesperrt. Wird in der Deklaration eines geschützten Typs das Schlüsselwort "type" nicht verwendet, so wird nur ein geschütztes Objekt deklariert. Es wird nur eine einzige Instanz angelegt. Dagegen wird durch das Schlüsselwort "type" wie üblich ein Typ definiert, mit dem Objekte deklariert werden können. Im folgenden soll die Bezeichnung "geschützter Typ" für den Typ wie auch für eine Objektvereinbarung stehen. Geschützte Unterprogramme und Eingänge können auch gleichzeitig in einem geschützten Typ verwendet werden. Geschützte Eingänge unterscheiden sich von geschützten Unterprogrammen dadurch, daß jene Eingangsaufrufe in einer Warteschlange stapeln können und diese Eingangs-aufrufe auch über einen bedingten bzw. befristeten Eingangsaufruf abgebrochen werden können. Die Aufrufe von geschützten Unterprogrammen können weder gestapelt noch abgebrochen werden. Beispiel:
Ein Drucker kann so mehrere gleichzeitige Aufträgen sequentialisieren. Die Bedingung am Eingang verhindert nicht, daß ein Eingangsaufruf in die Warteschlange eingereiht wird. Der Eingang "drucken" kann Druckaufträge in seiner Warteschlange stapeln. Für die Mitteilung, daß ein Druckauftrag beendet wurde und damit Freigabe für den nächsten, ist dagegen eine Warteschlange unnötig. Da alle Aktionen sequentialisiert sind, ist es unerheblich, zu welchem Zeitpunkt, während der Rumpf des Einganges "drucken" durchlaufen wird, die Bedingung "beim_drucken" auf "true" gesetzt wird. Lesen und Schreiben gemeinsamer Daten durch mehrere Prozesse kann durch Prozeduren und Funktionen eines geschützten Typs sequentialisiert werden. Sie werden mit dem Schlüsselwort "protected" versehen. Für eine geschützte Funktion ist die Instanz eines geschützten Typs konstant, d. h. alle Komponenten dieser Instanz sind konstant. Sie dürfen nur gelesen aber nicht verändert werden. Für eine geschützte Prozedur sind alle Komponenten dieser Instanz les- wie auch änderbar. Beispiel:
Die Instanz "eine_gemeinsame_variable" des geschützten Typs "gemeinsame_variable" verhindert durch Sequentialisierung der Zugriffe "lesen" und "schreiben", daß, während geschrieben wird, auch gelesen werden kann. Hinweis: Das Beispiel ist nicht realistisch, da durch die expliziten Prozeßwechsel ("delay 0.0"), derartige Zugriffe überhaupt nicht möglich sind. Sie sind (für das Testsystem) aber notwendig, um abwechselnd Schreiben und Lesen zu erreichen. Das Konstrukt "geschützter Typ" erlaubt nicht, außerhalb des mit "private" markierten Bereiches Variable zu deklarieren. Würde dies möglich sein, wäre eine solche Variable eine einfache gemeinsame Variable mit allen Nachteilen dieser. Aber diese sollen durch das Konstrukt des geschützten Typs gerade vermieden werden. Der Nachteil von geschützten Unterprogrammen ist, daß der Aufruf, z. B. zeitlich, nicht begrenzt werden kann. Falls der geschützte Typ die Unterprogramme nicht kurz faßt, sondern weitere Aufrufe, z. B. Prozeßeingänge aufruft oder langwierige Berechnungen ausführt, bleibt der aufrufende Prozeß blockiert. Geschützte Eingänge sind Eingänge von geschützten Typen und sie sind den Eingängen von Prozessen ähnlich. Alle Aufrufvarianten lassen sich verwenden. Allerdings gibt es keine Annahmeanweisung (accept), denn ein geschützter Typ ist kein Prozeß, der selbsttätig agieren kann. Als weitere Besonderheit muß jeder Eingangsrumpf eines geschützten Eingangs eine Bedingung aufweisen. Die Bedingungen an den Eingängen verhindern nicht, daß der Eingangsaufruf in die Warteschlange eingereiht wird, sondern nur, daß der Rumpf durchlaufen wird. Beispiel 1: (Übersetzer)
Der erste Eingang "lesen" wird durch eine Bedingung, Variable "moeglich" (A), gesichert. Diese Variable wird bei der Instantiierung des geschützten Typs auf "false" gesetzt (C). Damit wird verhindert, daß gelesen werden kann, bevor geschrieben wurde. Der zweite Eingang "schreiben" darf immer aufgerufen werden (B). Wird er aufgerufen, so wird die Variable "moeglich" auf "true" gesetzt und ab diesem Zeitpunkt kann immer gelesen werden. Wie bei Prozeßeingängen gilt, daß die Abarbeitung des Rumpfes eines Einganges möglichst kurz gehalten werden sollte, insbesondere sollten keine Prozeßeingänge aufgerufen oder Verzögerungsanweisungen verwendet werden, da sonst der aufrufende Prozeß blockiert wird. Beispiel 2: (Übersetzer)
Das Beispiel stellt einen Ausschnitt dar. "ein_prozeß" ruft den Eingang "lesen" des geschützten Typs so auf, daß er nur, wenn der Aufruf sofort akzeptiert wird, den Wert lesen will. Da, bevor der Eingang "schreiben" aufgerufen wurde, die Bedingung "moeglich" "false" ist, der Eingang diesen Aufruf nicht sofort akzpetieren kann, storniert "ein_prozeß" den Aufruf und arbeitet den Else-Zweig seiner Auswahlanweisung ab. Das Attribut "E'count" kann wie für Prozeßeingänge im Rumpf des geschützten Typs benutzt werden, um festzustellen, wieviele Aufrufe in der Warteschlange aufgereiht sind. Ebenso kann die Requeue-Anweisung benutzt werden, um Eingangsaufrufe auf andere Eingänge umzuleiten. Nach Abschluß (alle Anweisungen sind abgearbeitet) aber vor Beendigung einer geschützten Aktion, werden alle Eingänge des geschützten Typs bedient. Hiervon ist die geschützte Funktion ausgenommen, denn für eine solche ist der geschützte Typ eine Konstante. Die Bedingungen an allen Eingängen werden ausgewertet und, falls diese durch die vorangehende Aktion "true" geworden sind, werden alle Eingangsaufrufe auf einen Schlag zugelassen. Sie werden alle abgearbeitet bevor die Aktion beendet wird. Das ist einer der wesentlichen Unterschiede zu Eingängen von Prozessen. Die Sperrung eines geschützten Typs für neue Aktionen gilt auch für die Auswertung von Bedingungen an Eingängen. Solange eine Aktion abläuft, wird für keine neue Aktion eine Bedingung an einem Eingang ausgewertet. Dies hat zur Folge, daß bereits in den Warteschlangen aufgereihte Eingangsaufrufe vorrangig abgearbeitet werden, da die während einer Aktion neu eintreffenden Eingangsaufrufe nicht in die Warteschlangen eingetragen werden. Beispiel 3: (Übersetzer)
Das Programm produziert folgende Ausgaben: Wartende Aufrufe am Eingang lesen 3 Rumpf des Einganges lesen wird durchlaufen Rumpf des Einganges lesen wird durchlaufen Rumpf des Einganges lesen wird durchlaufen Wartende Aufrufe am Eingang lesen 0 Rumpf von prozess wird durchlaufen Rumpf von prozess wird durchlaufen Rumpf von prozess wird durchlaufen Nachdem die Prozesse "a_prozess", "b_prozess" und "c_prozess" je einen Eingangsaufruf auf das geschützte Objekt vom Typ "gemeinsame_variable" abgesetzt haben, der nicht ausgeführt werden kann, da die Bedingung "moeglich" den Wert "false" hat, sind sie blockiert. Dann wird der Rumpf des Hauptprogramms "prote4" durchlaufen. Erster Aufruf des Einganges "schreiben": Nach Abschluß aber vor Beendigung der Aktion wird die Bedingung des Einganges "lesen" ausgewertet und es werden alle drei wartenden Eingangsaufrufe bedient. Der Rumpf des Einganges "lesen" wird dreimal durchlaufen. Zweiter Aufruf des Einganges "schreiben": Es warten keine Eingangsaufrufe in der Warteschlange mehr. Erst danach wird durch einen Prozeßwechsel (das Hauptprogramm "prote4" ist fertig ausgeführt) einer der drei Prozesse "a_prozess", "b_prozess" oder "c_prozess" angestoßen und nacheinander die anderen beiden auch. Hinweis: Globale Variable in Wächtern zu Eingängen sind zu vermeiden. Wird die Bedingung an den Eingängen durch eine globale Variable beeinflußt, so ist nicht sichergestellt, daß eine Veränderung dieser Variable im Verlaufe der momentanen Aktion bei der Auswertung einer Bedingung berücksichtigt wird (aus dem Rumpf einer geschützten Aktion, die die globale Variable verändert hat, heraus wird ein interner Aufruf für einen Eingang gestartet). Es ist möglich, daß diese Änderung erst bei der nächsten neuen Aktion bekannt ist. Daher sollten globale Variable in Bedingungen zu Eingängen von geschützten Typen vermieden werden. Eine solche Programmierung ist insbesondere nicht portabel..öglich, daß diese Änderung erst bei der nächsten neuen Aktion bekannt ist. Daher sollten globale Variable in Bedingungen zu Eingängen von geschützten Typen vermieden werden. Eine solche Programmierung ist insbesondere nicht portabel. Ein geschütztes Objekt als Instantiierung eines geschützten Typs kann eine geringere Lebensdauer haben als die Objekte, die darauf zugreifen. Soll ein geschütztes Objekt beendet werden und sind noch Eingangsaufrufe in Eingängen zu diesem Objekt aufgereiht, so werden vorher alle diese Eingangsaufrufe aus den Wartenschlangen entfernt und für alle Prozesse, von denen diese Eingangsaufrufe abgesetzt wurden, die Ausnahme "program_error" ausgelöst. Während eine geschützte Aktion durch einen Prozeß ausgeführt wird, gelten alle Operationen, die möglicherweise zu einer Blockierung dieses Prozesses führen, als begrenzte Fehler. Alle folgenden Operationen können möglicherweise zu einer Blockierung führen:
Infolge einer blockierenden Aktion ist der geschützte Typ gegen alle weiteren Aktionen gesperrt, da jene nicht beendet werden kann. Beispiel 1:
Die Anweisung (A) zieht einen begrenzten Fehler nach sich, denn die Variable "moeglich" ist auf false gesetzt, wenn sie als Bedingung für den Eingang "lesen" gebraucht wird. Deshalb wird der Rumpf von lesen nicht durchlaufen. Beispiel 2:
Die Anweisung (B) zieht einen begrenzten Fehler nach sich, da sie ein externer Aufruf ist. Denn der Aufruf bezieht sich explizit auf das Objekt "gemeinsame_variable". Dagegen ist im Beispiel 1 Anweisung (A) ein interner Aufruf, der implizit für die momentane Instanz gilt.nach sich, da sie ein externer Aufruf ist. Denn der Aufruf bezieht sich explizit auf das Objekt "gemeinsame_variable". Dagegen ist im Beispiel 1 Anweisung (A) ein interner Aufruf, der implizit für die momentane Instanz gilt. |
|||||||||||
|