Home
 

Rendezvous mit Alternativen

Rendezvous mit Alternativen

Accept-Alternativen


<selective_accept> ::=
select
[when <condition> =>]
<selective_accept_alternative>


or
[when <condition> =>]
<selective_accept_alternative>


[else
<statement_sequence>]
end select;

<selective_accept_alternative> ::=
<accept_alternative>
| <delay_alternative>
| <terminate_alternative>

accept_alternative


<accept_alternative> ::=
<accept_statement> [<statement_sequence>]
mehrereerstenserver tasksverschiedene Dienste

task Server is
entry Service1(...);
...
entry ServiceN(...);
end Server;


task body Server is
...
begin
loop
select
accept Service1(...) do
...
end Service1;
- internal work 1 -
or
...
or
accept ServiceN(...) do
...
end ServiceN;
- internal work N -
end select;
end loop;
end Server;

Guards


select
when - Dienst möglich - =>
accept Service1(...) do
...
end Service1;
- interne Arbeit -
or
...
end select;

terminate Alternative


<terminate_alternative> ::= terminate;
Terminierungda sind

loop
select
accept Service1(...) do
...
end Service1;
or
...
or terminate;
end select;
end loop;

Methodische Bemerkung

Servern
  1. protected object mit service = protected operation oder entry
  2. task mit Endlosschleife + select accept service

Variante 1 (= passives Objekt) ist oft besser (einfaches Programm, effiziente Realisierung), es sei denn der Server muß zwischen den Diensten lokal Arbeit leisten (aufräumen, nächsten Dienst vorbereiten, ...), wodurch der Klient nicht verzögert werden sollte.

Beispiel: server task

[*]

-----------------------------------
- Aufgabe: producer-consumer (summation_of_naturals) with bounded buffer
- Methode: tasks for producer-consumer (simple sum_of_naturals problem)
- AND task for buffer
-----------------------------------


with Stringpack; use Stringpack;
procedure Bounded_Buffer2 is


task Buffer is - server task
entry Insert(D: Natural);
entry Take(D: out Natural);
end Buffer;


task Producer is
entry Start(How_Many: Natural);
end Producer;


task Consumer is
entry Start(Break: Natural);
end Consumer;


task body Producer is
Local_How_Many: Natural;
begin
accept Start(How_Many: Natural) do
Local_How_Many := How_Many;
end Start;


Consumer.Start(Local_How_Many +1);
for I in 1..(Local_How_Many +1) loop
Buffer.Insert(I);
end loop;
end Producer;


task body Consumer is
Over, Item, Result: Natural;
begin
accept Start(Break: Natural) do
Over := Break;
end Start;


Result := 0;
Buffer.Take(Item);
while Item /= Over loop
Result := Result +Item;
Buffer.Take(Item);
end loop;


Print(SSumme= " & Result);
end Consumer;


task body Buffer is
Length: constant Natural := 10;
B: array(0..Length-1) of Natural;
In_Ptr, Out_Ptr: Natural := 0;
Count: Natural := 0;
begin
loop
select
when Count < Length =>
accept Insert(D: Natural) do
B(In_Ptr) := D;
end Insert;


In_Ptr := (In_Ptr +1) mod Length;
Count := Count +1;
or
when Count > 0 =>
accept Take(D: out Natural) do
D := B(Out_Ptr);
end Take;


Out_Ptr := (Out_Ptr +1) mod Length;
Count := Count -1;
or
terminate;
end select;
end loop;
end Buffer;


N: Natural;
begin
Print("give natural");
N := Getint;
Producer.Start(N);
end Bounded_Buffer2;


-----------------------------------
45 pohlmann@mandrill:bounded_buffer2
give natural
100
Summe= 5050

Beispiel: Primzahlen durch Siebmethode

unendlichen ListenProzessenächste Element
  • Passive Objekte mit internem Zustand + give_next-Operation.
  • Aktive Objekte wie Ada task mit give_next-entry.

Hausaufgabe

  1. Die tasks, die die unendlichen Folgen repräsentieren, können nur einen Klienten bedienen (entry next = Einmal-Durchlauf). Überlegen Sie sich eine Realisierung, die beliebig viele Klienten bedienen kann (und dazu bereits erzeugte Folgenelemente jeweils speichert).
  2. In der Literatur (z.B. Barnes, Burns/Wellings) wird die Primzahl/Sieb-Aufgabe meist anders gelöst, nämlich mit umgekehrter Aufrufstruktur: Zahlenfolge stößt Nachfolger-Zahlenfolge an. Betrachten (realisieren) Sie eine solche Lösung und überlegen Sie Vor- und Nachteile!

Delay-Alternative


<delay_alternative> ::=
<delay_statement> [<sequence_of_statements>]


<delay_statement> ::= delay <expr>;
| delay until <expr>;
timeouts

else-Teil des select-statements

nicht sofort

... ...
select select
accept E; accept E;
or else
delay 0.0; Statement;
Statement; end select;
end select; ...
...

Man vermeide ,,polling`` d.h. ständiges Abfragen (busy waiting).


...
loop
select
accept E;
else
null;
end select;
end loop;
...

Sinnvolle Anwendungen von ,,else`` sind selten!

Entry-Call mit Alternative

Anders als bei selective-accept ist bei Aufrufen nur 1 Alternative möglich.

Nämlich:

Timed entry call


<timed_entry_call> ::=
select
<entry_call_alternative>
or
<delay_alternative>
end select;

<entry_call_alternative> ::=
<entry_call_statement>
[<statement_sequence>]

Also z.B.:


loop
select
Buffer.Take(X);
Consume(X);
or
delay 5.0;
raise Alarm; - exception
end select;
end loop;

Conditional entry call


<conditional_entry_call> ::=
select
<entry_call_alternative>
else
<statement_sequence>
end select;

Auswahl unter mehreren entry calls

nichtErsatzlösungen

  1. Umkehrung der Aufrufrichtung: call $\leftrightarrow$ accept.
    Oft unnatürlich!
  2. Einführung von Hilfstask als Zwischenglied, kann Probleme bei 1 lösen.
  3. Wiederversuch (polling) mit ,,sinnvollem`` Abstand (ungleich 0, eventuell random).

z.B.:


task type Server is
entry Service;
end Server;


Server_List: array(1..N) of Server;


task Client;


task body Client is
begin
L: loop
for I in Server_List'Range loop
select
Server(I).Service;
exit L;
else
null;
end select;
end loop;


delay Some_Ticks;
end loop L;
end Client;

  • Ähnlich: Mischung von entry-calls für tasks un protected objects.
  • Klar: Ersatz ist nicht dasselbe wie ein ordentliches select:
    Kleines Warteintervall$$Ineffizient
    Großes Warteintervall$$Langes Warten möglich (Prüfung selten)

Deadlock revisited

Verklemmung4

Dort: Gebrauch von gemeinsamen Variablen, z.B. 2 tasks haben jeweils sperrendes Signal gesetzt.

Hier: Verklemmungsgefahr bei Rendezvous.


  1. task T1 is task T2 is
    entry E1; entry E2;
    end T1; end T2;


    task body T1 is task body T2 is
    begin begin
    T2.E2; (*) (**) T1.E1;
    accept E1; (**) (*) accept E2;
    end T1; end T2;

  2. ... ...
    task body T1 is task body T2 is
    begin begin
    accept E1; accept E2;
    T2.E2; T1.E1;
    end T1; end T2;

Unterschied von 1 und 2:

1
Beide tasks unweigerlich verklemmt.
2
Keine Verklemmung, wenn es dritte task gibt, die E1 oder E2 aufruft. Allerdings: Den letzten beißt der Hund ...

Wichtig im Kampf gegen deadlocks:

  • Große Sorgfalt bei Entwurft/Verfeinerung von Algorithmen.
  • Vorher Nachdenken (möglicherweise Beweise führen) anstatt auf Tests verlassen. Deadlock kann selten sein, kann durch anderweitige Systemaktivität verdeckt werden, und grundsätzlich: ,,ewige`` Inaktivität kann empirisch nicht von ,,langer`` aber endlicher Wartezeit unterschieden werden.
  • Anwendung methodischer Regeln, falls für konkreten Fall passend, z.B.: Aufteilung in tasks, die ausschließlich entry-call-statements enthalten (,,clients``) und tasks, die ausschließlich entry-accept-statements enthalten (,,server``).

nextnextupuppreviouspreviouscontentscontents
Nächste Seite:BeispieleAufwärts:Vorlesungsskriptum zu Spezielle AnwendersprachenVorherige Seite:Ada Tasking IIInhaltsverzeichnis
Ronald Blaschke 2001-06-02