10. Parallele Prozesse
 
zurück
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.

with text_io;
procedure gemeinsam is
    gemeinsame_variable : integer := 0;
    -- (X)
    task type prozess;

    task body prozess is
    begin
       for zaehler in integer range 1 .. 10 loop
          gemeinsame_variable := zaehler;
          text_io.put_line( "prozess i= " & integer'image( gemeinsame_variable ) );
          delay 0.3;
       end loop;
    end prozess;

    -- Deklaration des expliziten Prozesses.
    ein_prozess : prozess;
begin
   -- Start des impliziten Prozesses (A).
   for zaehler in 1 .. 20 loop
      text_io.put_line( "gemeinsam i= " & integer'image( gemeinsame_variable ) );
      delay 0.1;
   end loop;
end gemeinsam;

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:
  • Ein Übersetzer könnte für den zweiten Prozeß (also ab (A)) eine lokale Kopie von "gemeinsame_variable" zur Optimierung anlegen, da ab (A) diese nur gelesen, aber nicht verändert wird.
    Ein Übersetzer kann eine solche Optimierung vornehmen, wenn der Zugriff auf "gemeinsame_variable" sehr aufwendig ist, z. B. wenn diese im Speicher eines anderen Prozessors steht. Bei einer solchen Optimierung könnte der implizite Prozeß nicht die Veränderungen, die durch den expliziten Prozeß verursacht werden, ausgeben.
  • Während der Ausgabe von "gemeinsame_variable" durch den impliziten Prozeß wird der Wert von "gemeinsame_variable" vom expliziten Prozeß verändert. Dies ist möglich, wenn "gemeinsame_variable" eine umfangreiche strukturierte Variable ist.
    Dann setzt sich die Ausgabe aus Teilen verschiedener Werte von "gemeinsame_variable" zusammen.
Beispiel 2:

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.

   -- ...
   gemeinsame_variable : boolean := true;
   -- ...
   procedure manipuliere_ressource is
      -- ...
   begin
      -- ...
   end manipuliere_ressource;
   -- ...
begin
   -- ...
   if gemeinsame_variable then -- (A)
      gemeinsame_variable := false; -- (B)
      manipuliere_ressource;
      gemeinsame_variable := true;
   end if;
   -- ...

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

pragma atomic( gemeinsame_variable );


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:

protected type drucker is
   entry drucken( text : in string );
   procedure drucken_beendet;
private
   beim_drucken : boolean := false;
end drucker;

protected body drucker is
   entry drucken when not beim_drucken is
   begin
      beim_drucken := true;
   end drucken;
   procedure drucken_beendet is
   begin
      beim_drucken := false;
   end drucken_beendet;
end drucker;


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:

with text_io;
procedure prot is
   -- Deklaration des geschuetzten Typs.
   protected type gemeinsame_variable is
      function lesen return integer;
      procedure schreiben( x : in integer );
   private
      variable : integer := 0;
   end gemeinsame_variable;
   -- Implementierung des geschuetzten Typs.
   protected body gemeinsame_variable is
      function lesen return integer is
      begin
         -- "variable" ist fuer die Funktion "lesen" eine Konstante
         return variable;
      end lesen;
      procedure schreiben( x : in integer ) is
      begin
         variable := x;
      end schreiben;
   end gemeinsame_variable;
   -- Instantiierung der gemeinsamen Variablen.
   eine_gemeinsame_variable : gemeinsame_variable;
   -- Spezifikation eines Prozesses.
   task type prozess;
   -- Implementierung des Prozesses.
   task body prozess is
      i : integer := 0;
    begin
       loop
          i := eine_gemeinsame_variable.lesen;
          exit when i < 0; -- Abbruch
          text_io.put_line( "prozess i= " & integer'image( i ) );
          delay 0.0; -- Prozesswechsel einleiten.
       end loop;
    end prozess;
    -- Deklaration des expliziten Prozesse.
    ein_prozess : prozess;
begin
   -- Start des impliziten Prozesse.
   for i in integer range 1 .. 10 loop
      eine_gemeinsame_variable.schreiben( i );
      delay 0.0; -- Prozesswechsel einleiten.
   end loop;
   -- Uebergeben des Abbruchkriteriums.
   eine_gemeinsame_variable.schreiben( -1 );
end prot;


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)

with text_io;
procedure prote is
   -- Deklaration des geschuetzten Typs.
   protected type gemeinsame_variable is
      entry lesen ( x : out integer );
      entry schreiben( x : in integer );
   private
      variable : integer := 0;
      moeglich : boolean := false; -- (C)
   end gemeinsame_variable;
   -- Implementierung des geschuetzten Typs.
   protected body gemeinsame_variable is
      entry lesen( x : out integer ) when moeglich is -- (A)
      begin
         x := variable;
      end lesen;
      entry schreiben( x : in integer ) when true is -- (B)
      begin -- (D)
         variable := x;
         moeglich := true;
      end schreiben;
   end gemeinsame_variable;
   -- Instantiierung der gemeinsamen Variablen.
   eine_gemeinsame_variable : gemeinsame_variable;
   -- Spezifikation eines Prozesses.
   task type prozess;
   -- Implementierung des Prozesses.
   task body prozess is
      i : integer := 0;
   begin
      loop
         delay 0.001; -- Prozesswechsel einleiten.
         eine_gemeinsame_variable.lesen( i );
         text_io.put_line( "prozess i= " & integer'image( i ) );
         exit when i < 0; -- Abbruch
      end loop;
   end prozess;
   -- Deklaration des expliziten Prozesses.
   ein_prozess : prozess;
begin
   -- Start des impliziten Prozesses.
   for i in integer range 1 .. 10 loop
      eine_gemeinsame_variable.schreiben( i );
      delay 0.001; -- Prozesswechsel einleiten.
   end loop;
   -- Uebergeben des Abbruchkriteriums.
   eine_gemeinsame_variable.schreiben( -1 );
end prote;


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)

-- ...
task body prozess is
   i : integer := 0;
begin
   loop
      delay 0.001; -- Prozesswechsel einleiten.
      select -- Bedingung mit Else-Zweig.
         eine_gemeinsame_variable.lesen( i );
         text_io.put_line( "prozess i= " & integer'image( i ) );
      else
         text_io.put_line( "prozess else" ); -- (A).
      end select; exit when i < 0; -- Abbruch
   end loop;
end prozess;
-- ...

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)

with text_io;
procedure prote4 is
   -- Deklaration des geschuetzten Typs.
   protected type gemeinsame_variable is
      entry lesen ( x : out integer );
      entry schreiben( x : in integer );
   private
      variable : integer := 0;
      moeglich : boolean := false;
   end gemeinsame_variable;
   -- Implementierung des geschuetzten Typs.
   protected body gemeinsame_variable is
      entry lesen( x : out integer ) when moeglich is
      begin
         text_io.put_line( "Rumpf des Einganges lesen wird durchlaufen" );
         x := variable;
      end lesen;

      entry schreiben( x : in integer ) when true is
      begin
         text_io.put_line( "Wartende Aufrufe am Eingang lesen" & integer'image( lesen'count ) );
         variable := x;
         moeglich := true;
      end schreiben;
   end gemeinsame_variable;
   -- Instantiierung der gemeinsamen Variablen.
   eine_gemeinsame_variable : gemeinsame_variable;
   -- Spezifikation eines Prozesses.
   task type prozess;
   -- Implementierung des Prozesses.
   task body prozess is
      i : integer := 0;
   begin
      eine_gemeinsame_variable.lesen( i );
      text_io.put_line( "Rumpf von prozess wird durchlaufen" );
   end prozess;
  
   -- Deklaration der expliziten Prozesse.
   a _prozess : prozess;
   b_prozess : prozess;
   c_prozess : prozess;

begin
   -- Start des impliziten Prozesses.
   eine_gemeinsame_variable.schreiben( 1 );
   eine_gemeinsame_variable.schreiben( 2 );
end prote4;


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:
  • Auswahlanweisung,
  • Annahmeanweisung,
  • Eingangsaufruf,
  • Verzögerungsanweisung,
  • Abort-Anweisung,
  • Anlegen eines Prozesses oder dessen Aktivierung,
  • externer Aufruf eines geschützten Unterprogrammes für dasselbe Objekt oder entsprechend eine
    Requeue-Anweisung, die ein externer Aufruf ist,
  • Aufruf eines Unterprogrammes, dessen Rumpf eine möglicherweise blockierende Operation
    enthält.
Neben den genannten Operationen können auch die Ein- und Ausgabe möglicherweise blockierend wirken !

Infolge einer blockierenden Aktion ist der geschützte Typ gegen alle weiteren Aktionen gesperrt, da jene nicht beendet werden kann.


Beispiel 1:

-- ...
entry lesen( x : out integer ) when moeglich is
begin
   x := variable;
end lesen;
-- ...
entry schreiben( x : in integer ) when true is
   i : integer := 0;
begin
   variable := x;
   lesen( i ); -- (A)
   moeglich := true;
end schreiben;
-- ...


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:

-- ...
entry schreiben( x : in integer ) when true is
   i : integer := 0;
begin
   variable := x;
   moeglich := true;
   gemeinsame_variable.lesen( i ); -- (B)
end schreiben;
-- ...


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.


 
zurück
 Index   Ada Tour - Dokumentation  
© 2003 Förderverein Ada Deutschland e.V.