Home
 

Ada Tasking II

Ada Tasking II

Immer noch nicht volle Syntax, sondern nur Basisform von rendezvous und protected objects; mehr später.

Tasks

  • Haben ähnlich wie packages Spezifikationsteil und Rumpf, müssen aber in übergeordnetem Programmteil (z.B. Hauptprogramm, Paket) deklariert werden, sind also selber nicht Bibliothekseinheiten!
  • Beinhalten wie Unterprogramme eigene Deklarationen und eine Anweisungsfolge, werden aber nicht durch Aufruf gestartet (und im Kontrollfluß des Aufrufers ausgeführt), sondern implizit und konkurrent zu anderen.
  • Lassen sich wie Datenstrukturen unterscheiden in Typ (z.B. type vector is array(1..n) of ...) und zugehörige Objekte (z.B. v1, v2: vector), sind aber nicht passiv, sondern haben eigenen Kontrollfluß!

Wir betrachten den letzten Punkt. Bisher nur ,,Einmal-tasks``, die einen anonymen Typ haben!


task Hund;
task body Hund is
print("wau wau");
end Hund;

Allgemein deklariert man den Typ mit


task type T is
...
end T;


task body T is
...
end T;

t1, t2: T;

Task-Typen sind limited, d.h. Vergleich und Zuweisung ist nicht erlaubt. (Task Objekte also insbesondere konstant).

Task Typen erlauben wie andere Typen die Bildung von zusammengesetzten Datenstrukturen, z.B.


T_Vector: array(1..10) of T;
dynamische Erzeugung

type T_pointer is access T;
p: T_pointer;
...
begin
...
p := new T;
...
end;

Beispiele

  • Array of tasks
    Gebäudeheizung mit Sensor-task für jeden Raum.
  • Dynamische Erzeugung
    Luftraumüberwachung mit task für jedes gesteuerte Flugzeug.

Verschiedene Tasks desselben Typs

  • Zeigen gleichartiges Verhalten.
  • Haben ihre eigenen Daten und Kontrollfluß (speziell: 2 Tasks rufen dieselbe Prozedur auf $\Rightarrow$ 2 Inkarnationen der Prozedur, wie bei Rekursion).

Aktivierung, Ausführung, Terminierung

  • Ist ein komplexes Thema, weil so viele Kontexte möglich.
  • Wir beschränken uns auf einfache/fundamentale Fälle (und warnen vor exotischen Konstruktionen).

Task Objekt deklariert in Hauptprogramm


procedure Main is
...
task type T;
A: T;
B: T;
...
begin
...
end;

dann

  • Hauptprogramm wird angesehen als von einer ,,Umgebungs-Task`` aufgerufen und ausgeführt. Dies ist die ,,Mutter`` (der ,,Vater``) der im Hauptprogramm deklarierten Tasks, die von ihr abhängen (,,are dependent on``).
  • Deklarierte Task Objekte werden
    aktiviert
    Ihre internen Deklarationen werden elaboriert.
    exekutiert
    Anweisungsfolge wird ausgeführt.

\includegraphics{aktExeTasks.eps}

Hauptprogramm arbeitet erst selbst, wenn Aktivierung aller Tasks gelungen ist. Grund: Fehler auffangen.

Task deklariert in (library-) Package

  • Wird aktiviert mit ,,begin`` des Paketrumpfs; gibt es dort keinen Anweisungsteil, wird ,,begin null; end;`` angenommen.
  • Main-Environment-Task terminiert erst, wenn
    1. Hauptprogramm terminiert.
    2. Library (package) Task terminiert.

Task wird dynamisch erzeugt durch Allokator

  • Aktivierung während Ausführung der Allokationsanweisung, dann Ausführung konkurrent zum Erzeuger.
  • Solche Tasks hängen nicht ab vom Erzeuger, sondern von Programmeinheit, die den zugehörigen access-Typ eingeführt hat!

Rendezvous

  • Ist Mittel zur Synchronisation und Kommunikation.
  • Geschieht über im Task-Spezifikationsteil deklarierte ,,entries``

    task type T is
    entry E(<Param. Spec.>);
    end T;


    task body T is
    ...
    accept E(<Param. Spec.>) do
    ...
    end E;
    end T;
  • Gebrauch
    Sei T1:T;, dann Aufruf des entries mit T1.E(<Actuals>);
  • Wirkung
    • Parameterübergabe (in, out, inout) ähnlich Prozedur.
    • Aufrufer wartet während Ausführung des accept-statements, ähnlich Prozedur.
    • Aufrufer wartet, bis Aufgerufener accept-statement ausführt.
    • Aufgerufener wartet, bis jemand entry aufruft.

    \includegraphics{rendezvous.eps}

  • Task body kann mehrere accept-statements für dasselbe entry enthalten (insbesondere in Schleife - typischer Fall einer Server-Task).
  • Entry kann von mehreren Tasks gerufen werden $\Rightarrow$ Warteschlange!
  • Accept-statement muß direkt im task body stehen, aber nicht in Unterprogramm. (Aufruf aber durchaus in Unterprogramm. Möglich und manchmal sinnvoll: procedure ...renames entry ...).
  • Accept statement sollte nur Anweisungen enthalten, die für das Rendezvous (Ergebnisrückgabe) unbedingt nötig sind - also den Anrufer nicht sinnlos verzögern.
  • Entries ohne Parameter sind nützlich für Synchronisationszwecke, z.B.

    -----------------------------------
    - Demo: rendezvous zur Synchronisation
    -----------------------------------


    with stringpack; use Stringpack;
    procedure Tierwelt2 is


    task type Hund is
    entry Los;
    end Hund;


    task body Hund is
    begin
    accept Los;
    Print("wau");
    Print("wau");
    end Hund;


    Rex, Pluto, Fifi: Hund;


    task Katz is
    entry Los;
    entry Nochmal;
    end Katz;


    task body Katz is
    begin
    accept Los;
    Print(miau");
    accept Nochmal;
    Print(miau");
    end Katz;


    begin
    Print("hello world");
    Rex.Los; Fifi.Los; Katz.Los; Pluto.Los; Katz.Nochmal;
    end Tierwelt2;

Synchronisation mittels Rendezvous kann man benutzen, um

  • Andere Synchronisationsmittel wie Semaphore zu realisieren, oder
  • die mit z.B. Semaphoren lösbaren Aufgaben direkt anzugehen.

Letzteres ist vorzuziehen, wobei - wie später besprochen -

  • Verfeinerung der Rendezvous Konstruktion
  • Protected types
w ichtig sind für gute Lösung.

Hier einstweilen eine schlechte Lösung.

  • Imitiere (Binäres-) Semaphor.
  • Löse damit Problem des wechselseitigen Ausschlusses.

Beispiel: Imitation einer binären Semaphore

3.4[*]

So nicht!


with Stringpack, Ada.Calendar;
use Stringpack, Ada.Calendar;


procedure Shared_Demo3 is


Start_Work, End_Work: Time;


N: Integer := 0; -shared variable
Anz: Positive := 1_000_000;


task Binary_Semaphore is
entry Wait;
entry Signal;
end Binary_Semaphore;


task body Binary_Semaphore is
begin
loop
accept Wait;
accept Signal;
end loop;
end Binary_Semaphore;


task Up is
entry Start;
entry Finish;
end Up;


task body Up is
My_N: Integer;
begin
accept Start;
for I in 1..Anz loop
Binary_Semaphore.Wait;
My_N := N;
My_N := My_N+1;
N := My_N;
Binary_Semaphore.Signal;
end loop;
accept Finish;
end Up;


task Down is
entry Start;
entry Finish;
end Down;


task body Down is
My_N: Integer;
begin
accept Start;
for I in 1..Anz loop
Binary_Semaphore.Wait;
My_N := N;
My_N := My_N-1;
N := My_N;
Binary_Semaphore.Signal;
end loop;
accept Finish;
end Down;


begin
Start_Work := Clock; - Zeiterfassung
Up.Start; Down.Start;
Up.Finish; Down.Finish;
Print(n= " & N);
End_Work := Clock; - Zeiterfassung
Print(time spent= " & Float(Ada.Calendar."(End_Work, Start_Work)));
abort Binary_Semaphore; - sehr schlechter Stil !!!!!!!!!!!!!!!
end Shared_Demo3;


-----------------------------------
11 pohlmann@pavian:shared_demo3
n= 0
time spent= 1086.25


rbla@bravo:shared_demo3
n= 0
time spent= 424.29 (AMD K6, 200MHz, GNAT, Linux)

Zur Zeitmessung

Wall-clock-timenichtmein

Beispiel für Kooperation von Tasks

$\sum^n_{i=1}i$

Beispiel ist an sich nicht sinnvoll weil

  • Formel,
  • Arbeitsteilung nur bei echter Parallelität schneller,
a ber hier Zweck: Zeige verschiedene Möglichkeiten der Interaktion durch Rendezvous!

Beispiel 1

\includegraphics{rendBsp1.eps}


-----------------------------------
- Aufgabe: summiere 1..n.
- Methode: tasks fuer Teilsummen 1..n/2, n/2+1..n,
- Start u. Zusammenfassung durch Hauptprogramm
-----------------------------------
with Stringpack; use Stringpack;
procedure Arbeitsteilung is


task type Worker is
entry Start(From, To: Integer);
entry Result(Res: out Integer);
end Worker;


task body Worker is
Local_From, Local_To, Local_Res: Integer;
begin
accept Start(From, To: Integer) do
Local_From := From;
Local_To := To;
end Start;
Local_Res := 0;
for I in Local_From..Local_To loop
Local_Res := Local_Res+I;
end loop;
accept Result(Res: out Integer) do
Res := Local_Res;
end Result;
end Worker;


W1, W2: Worker;
N, Sum1, Sum2: Integer;


begin
Print("give natural");
N := Getint;
W1.Start(1, N/2);
W2.Start(N/2 +1, N);
W1.Result(Sum1);
W2.Result(Sum2);
Print(SSumme= " & (Sum1 + Sum2));
end Arbeitsteilung;


-----------------------------------
7 pohlmann@pavian:Arbeitsteilung
give natural
100
Summe= 5050

Beispiel 2

\includegraphics{rendBsp2.eps}


-----------------------------------
- Aufgabe: summiere 1..n.
- Methode: tasks fuer Teilsummen 1..n/2, n/2+1..n
- erste task startet die zweite und bildet Gesamtergebnis
-----------------------------------


with Stringpack; use Stringpack;
procedure Arbeitsteilung2 is


task Worker is
entry Start(From, To: Integer);
entry Result(Res: out Integer);
end Worker;


task Helper is
entry Start(From, To: Integer);
entry Result(Res: out Integer);
end Helper;


task body Worker is
Local_From, Local_To, Local_Res, Remote_Res: Integer;
begin
accept Start(From, To: Integer) do
Local_From := From;
Local_To := To;
end Start;
Helper.Start(Local_From, (Local_From + Local_To)/2);
Local_Res := 0;
for I in ((Local_From + Local_To)/2 +1)..Local_To loop
Local_Res := Local_Res+I;
end loop;
Helper.Result(Remote_Res);
accept Result(Res: out Integer) do
Res := Local_Res + Remote_Res;
end Result;
end Worker;


task body Helper is
Local_From, Local_To, Local_Res: Integer;
begin
accept Start(From, To: Integer) do
Local_From := From;
Local_To := To;
end Start;
Local_Res := 0;
for I in Local_From..Local_To loop
Local_Res := Local_Res+I;
end loop;
accept Result(Res: out Integer) do
Res := Local_Res;
end Result;
end Helper;


N, Sum: Integer;


begin
Print("give natural");
N := Getint;
Worker.Start(1, N);
Worker.Result(Sum);
Print(SSumme= " & Sum);
end Arbeitsteilung2;


-----------------------------------
16 pohlmann@pavian:Arbeitsteilung2
give natural
100
Summe= 5050

Beispiel 3


-----------------------------------
- Aufgabe: summiere 1..n.
- Methode: tasks fuer Teilsummen 1..n/2, n/2+1..n
- erste task startet die zweite und bildet Gesamtergebnis
- mit three-way-rendezvous
-----------------------------------


with Stringpack; use Stringpack;
procedure Arbeitsteilung2a is


task Worker is
entry Start(From, To: Integer);
entry Result(Res: out Integer);
end Worker;


task Helper is
entry Start(From, To: Integer);
entry Result(Res: out Integer);
end Helper;


task body Worker is
Local_From, Local_To, Local_Res, Remote_Res: Integer;
begin
accept Start(From, To: Integer) do
Local_From := From;
Local_To := To;
end Start;
Helper.Start(Local_From, (Local_From + Local_To)/2);
Local_Res := 0;
for I in ((Local_From + Local_To)/2 +1)..Local_To loop
Local_Res := Local_Res+I;
end loop;
accept Result(Res: out Integer) do -Rendezvous zu dritt
Helper.Result(Remote_Res);
Res := Local_Res + Remote_Res;
end Result;
end Worker;


task body Helper is
Local_From, Local_To, Local_Res: Integer;
begin
accept Start(From, To: Integer) do
Local_From := From;
Local_To := To;
end Start;
Local_Res := 0;
for I in Local_From..Local_To loop
Local_Res := Local_Res+I;
end loop;
accept Result(Res: out Integer) do
Res := Local_Res;
end Result;
end Helper;


N, Sum: Integer;


begin
Print("give natural");
N := Getint;
Worker.Start(1, N);
Worker.Result(Sum);
Print(SSumme= " & Sum);
end Arbeitsteilung2a;


-----------------------------------
30 pohlmann@pavian:Arbeitsteilung2a
give natural
100
Summe= 5050

Beispiel 4

aktiv

\includegraphics{rendBsp4.eps}


-----------------------------------
- Aufgabe: summiere 1..n.
- Methode: tasks fuer Teilsummen 1..n/2, n/2+1..n
- erste task startet die zweite und bildet Gesamtergebnis
- mit call-back procedure
-----------------------------------


with Stringpack; use Stringpack;
procedure Arbeitsteilung2b is


task Worker is
entry Start(From, To: Integer);
entry Result(Res: out Integer);
entry Call_Back(Res: Integer);
end Worker;


type Call_Back_Proc is access procedure(X: Integer);


procedure Call_Back_Worker(R: Integer) is -proc must not be deeper than access type
begin
Worker.Call_Back(R);
end Call_Back_Worker;


task Helper is
entry Start(From, To: Integer; Cb: Call_Back_Proc);
end Helper;


task body Worker is
Local_From, Local_To, Local_Res, Remote_Res: Integer;
begin
accept Start(From, To: Integer) do
Local_From := From;
Local_To := To;
end Start;
Helper.Start(Local_From, (Local_From + Local_To)/2, Call_Back_Worker'access);
Local_Res := 0;
for I in ((Local_From + Local_To)/2 +1)..Local_To loop
Local_Res := Local_Res+I;
end loop;
accept Call_Back(Res: Integer) do
Remote_Res := Res;
end Call_Back;
accept Result(Res: out Integer) do
Res := Local_Res + Remote_Res;
end Result;
end Worker;


task body Helper is
Local_From, Local_To, Local_Res: Integer;
My_Call_Back_Proc: Call_Back_Proc;
begin
accept Start(From, To: Integer; Cb: Call_Back_Proc) do
Local_From := From;
Local_To := To;
My_Call_Back_Proc := Cb;
end Start;
Local_Res := 0;
for I in Local_From..Local_To loop
Local_Res := Local_Res+I;
end loop;
My_Call_Back_Proc(Local_Res);
end Helper;


N, Sum: Integer;


begin
Print("give natural");
N := Getint;
Worker.Start(1, N);
Worker.Result(Sum);
Print(SSumme= " & Sum);
end Arbeitsteilung2b;


-----------------------------------
47 pohlmann@pavian:Arbeitsteilung2b
give natural
100
Summe= 5050

Klar: Helper Task könnte auch direkt entsprechendes entry von Worker Task aufrufen, aber dann wäre das Rückgabe-Ziel fest in den Programmtext von Helper zu schreiben, d.h. Helper könnte nicht von mehreren Klienten-Tasks benutzt werden!

Beispiel 5

dynamisch

-----------------------------------
- Aufgabe: summiere 1..n.
- Methode: tasks fuer Teilsummen 1..n/2, n/2+1..n falls n>50,
- erste task erzeugt und startet die zweite und bildet
- Gesamtergebnis
-----------------------------------


with Stringpack; use Stringpack;
procedure Arbeitsteilung3 is


task Worker is
entry Start(From, To: Integer);
entry Result(Res: out Integer);
end Worker;


task type Helper is
entry Start(From, To: Integer);
entry Result(Res: out Integer);
end Helper;


type Helper_Ptr is access Helper;


task body Worker is
Local_From, Local_To: Integer;
Local_Res, Remote_Res: Integer := 0;
H: Helper_Ptr;
begin
accept Start(From, To: Integer) do
Local_From := From;
Local_To := To;
end Start;
if Local_To-Local_From >50 then
H := new Helper;
H.Start(Local_From, (Local_From+Local_To)/2);
for I in ((Local_From + Local_To)/2 +1)..Local_To loop
Local_Res := Local_Res+I;
end loop;
H.Result(Remote_Res);
else
for I in Local_From+Local_To..Local_To loop
Local_Res := Local_Res + 1;
end loop;
end if;
accept Result(Res: out Integer) do
Res := Local_Res + Remote_Res;
end Result;
end Worker;


task body Helper is
Local_From, Local_To, Local_Res: Integer;
begin
accept Start(From, To: Integer) do
Local_From := From;
Local_To := To;
end Start;
Local_Res := 0;
for I in Local_From..Local_To loop
Local_Res := Local_Res+I;
end loop;
accept Result(Res: out Integer) do
Res := Local_Res;
end Result;
end Helper;


N, Sum: Integer;


begin
Print("give natural");
N := Getint;
Worker.Start(1, N);
Worker.Result(Sum);
Print(SSumme= " & Sum);
end Arbeitsteilung3;


-----------------------------------
21 pohlmann@pavian:Arbeitsteilung3
give natural
100
Summe= 5050

Beispiel 6

rekursiv

\includegraphics{rendBsp6.eps}


-----------------------------------
- Aufgabe: summiere 1..n.
- Methode: tasks fuer Teilsummen aus 10 Elementen,
- tasks nach Bedarf rekursiv erzeugt und gestartet,
- Ergebnis wandert zurueck durch Erzeugertasks
-----------------------------------


with Stringpack; use Stringpack;
procedure Arbeitsteilung4 is


task type Worker is
entry Start(From, To: Integer);
entry Result(Res: out Integer);
end Worker;


type Worker_Ptr is access Worker;


function Get_New_Worker return Worker_Ptr is -Hilfsfunktion, erzeugt neue task
begin
return new Worker;
end Get_New_Worker;


task body Worker is
Local_From, Local_To: Integer;
Local_Res, Remote_Res: Integer := 0;
Colleague: Worker_Ptr;
begin
accept Start(From, To: Integer) do
Local_From := From;
Local_To := To;
end Start;
if Local_To-Local_From >10 then
Colleague := Get_New_Worker; -task type cannot be used as type mark within its own body
Colleague.Start(Local_From+10, Local_To);
for I in Local_From..(Local_From+9) loop
Local_Res := Local_Res+I;
end loop;
Colleague.Result(Remote_Res);
else
for I in Local_From..Local_To loop
Local_Res := Local_Res+I;
end loop;
end if;
accept Result(Res: out Integer) do
Res := Local_Res+Remote_Res;
end Result;
end Worker;


N, Sum: Integer;
W: Worker_Ptr := new Worker;


begin
Print("give natural");
N := Getint;
W.Start(1, N);
W.Result(Sum);
Print(SSumme= " & Sum);
end Arbeitsteilung4;


-----------------------------------
49 pohlmann@pavian:Arbeitsteilung4
give natural
100
Summe= 5050
53 pohlmann@pavian:Arbeitsteilung4
give natural
1000
Summe= 500500
54 pohlmann@pavian:Arbeitsteilung4
give natural
1000000
^C - nach 5 Minuten warten ....................................

Hausaufgabe

Fließband-Verarbeitung

\includegraphics{pipeline.eps}

Beispiel für sowas könnte sein:

Protected types & protected objects

Syntax für Deklaration


protected type P is
<protected Operations>
private
<protected Data Declarations>;
end P;


protected body P is
<Impl. protected Operations>
end P;

  • Es gibt wieder ,,einmal`` Objekte vom anonymen Typ.
  • Der private-Teil mit den Attributen des Objekts kann auch fehlen.
  • Gebrauch wie üblich mit Typen - z.B. in Objektdeklaration p1: P; - Protected types sind limited, d.h. kein Vergleich, keine Zuweisung.

Semantik

Protected procedures
Erlauben zustandsändernden Zugriff und werden unter wechselseitigem Ausschluß ausgeführt, d.h. Prozedur hat Objekt für sich allein, andere Aufrufe einer Operation warten.
Protected functions
Mehrere Aufrufe gleichzeitig möglich, da nur ,,read-only``. Aber nicht zugleich mit Prozedur!

Mit protected objects lassen sich mutual exclusion Probleme einfach und effizient lösen, siehe Beispiel auf Seite [*].

Wichtige Einschränkung

nichtpotentiell blockierend

Bedingungssynchronisation (conditional synchronisation)


Beispiel: Puffer schreiben erfordert: Puffer nicht voll.

Dafür erlauben protected objects:

Tasking & Exceptions

tasking-error

Hausaufgabe


with Stringpack; use Stringpack;
procedure Exception_Demo6 is


task T1;
task T2 is
entry Hallo;
end T2;


task body T1 is
begin
for I in 1..3 loop
T2.Hallo;
end loop;


exception
when others => Print(t1: Fehler in rendezvous");
end T1;


task body T2 is
function F return Natural is begin return 0; end F;
N: Natural;
begin
for I in 1..3 loop
accept Hallo do
N := 1 mod F; - 1/0 in Rendezvous


exception
when Constraint_Error => Print(t2: Fehler in rendezvous");
end Hallo;
end loop;
end T2;


begin
null;
exception
when Constraint_Error => Print(main: Fehler in rendezvous");
end Exception_Demo6;


-----------------------------------
37 pohlmann@pavian:exception_demo6
t2: Fehler in rendezvous
Floating exception

Exkurs: Diskriminanten (= Parameter für Typen)

  • sind Typkomponenten mit speziellen Eigenschaften.
  • gibt es für zusammengesetzte Typen (außer für arrays!).
  • müssen selber von diskreten oder access Typ sein!

Klassischer Fall: Record Typ mit Diskriminante

Task types mit Diskriminante