Ada Tasking II
Ada Tasking II
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.
dynamische Erzeugung
T_Vector: array(1..10) of T;
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 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.
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
- Hauptprogramm terminiert.
- 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.
- 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 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
Hier einstweilen eine schlechte Lösung.
- Imitiere (Binäres-) Semaphor.
- Löse damit Problem des wechselseitigen Ausschlusses.
Beispiel: Imitation einer binären Semaphore
3.4So 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-timenichtmeinBeispiel für Kooperation von Tasks
Beispiel ist an sich nicht sinnvoll weil
- Formel,
- Arbeitsteilung nur bei echter Parallelität schneller,
Beispiel 1
-----------------------------------
- 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
-----------------------------------
- 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
-----------------------------------
- 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
-----------------------------------
- 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
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 blockierendBedingungssynchronisation (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