Tartalom:
- Védett képernyohasználat
- Taszkok inicializálása
- Védett objektumok és taszkok kapcsolata (pl kötélhúzás)
- Taszkok közti kommunikáció (pl zh írásnál diák-gyakvez)
- Taszkok hívása
- Taszkok fogadása
- Idokorlátos foprogram
Védett képernyőhasználat
Erre szinte mindig szükség van (akkor is, ha nem képernyőre, hanem szövegfájlba írjuk a történéseket), hogy még véletlenül se fordulhasson elő, hogy két taszk "egymás szavába vág", azaz egyik taszk még akkor elkezd kiírni valamit, mielőtt a másik befejezné, és így értelmetlenné válna a kiírt szöveg. (Gyakorlatilag egyébként ritkán fordul elő ilyesmi, de beadandóban és a ZH-n is elvárják a használatát.)
protected scr is procedure kiir (s : string); end scr; protected body scr is procedure kiir (s : string) is begin put_line(s); end kiir; end scr;
Ezután a Put_Line("valami"); helyett az scr.kiir("valami"); parancsot kell majd alkalmazni.
A kiíratásokat célszerű vagy csak a védett objektumban, vagy csak a taszkokban (azon belül is minél kevesebb félében) elintézni, vegyesen kevésbé átlátható. (Ha taszkok ill. a védett obejktumok függvényei nem végeznek kiiratást, akkor nincs is feltétlen szükség a védett képernyőhasználatra)
Ha taszk típusokat használunk, akkor a taszkjainkat szinte mindig inicializálnunk kell, ahol megkapják az adattagjaik a kezdőértéküket (a feladatok többségében valamilyen random értékként), valamint egy azonosítót, hogy meg tudjuk őket különböztetni (erre a kiíratásoknál szükség lesz minden bizonnyal, de gyakran előfordul, hogy máshol is fel kell ezt használni).
Ekkor a típus definicíójában el kell helyezni egy entry pontot pl init névvel, a szükséges diszkriminánsokkal ellátva, majd a törzs részében a taszk begin-je után kell egy accept init (diszkriminánsok), amiben beállítja magának a saját adattagjait. Ez után kezdheti el végezni a tényleges dolgát, hiszen a főprogram begin-jénél ugyan elindul az összes taszk, de először megvárja, hogy (általában) a főprogram az adott taszkra meghívja az init utasítást.
procedure main is ... task type versenyzo is entry init (csapat : in boolean; azonosito : in integer); end versenyzo; task body versenyzo is id : integer; hazai : boolean; begin accept init (csapat : in boolean; azonosito : in integer) do id := azonosito; hazai := csapat; end init; loop ... end loop; end versenyzo; v: array (1..10) of versenyzo; ... begin for i in 1..5 loop v(i).init(true,i); end loop; for i in 6..10 loop v(i).init(false,i); end loop; ... end main;
Védett objektumok és taszkok kapcsolata
Ha a védett objektum használata időt igényel (a legtöbb feladatnál ez így szokott lenni), akkor ne abba rakjuk a delay-t, hanem a taszkba, ami őt használja, a védett objektumban pedig legyen egy rejtett változó, hogy foglalt-e. Ekkor kell külön egy eljárás az elkezdéséhez (ahol foglalttá válik), és egy másik a befejezéshez (ahol felszabadul, és persze közben történik is benne valami az egyik eljárásban). A taszkban pedig előbb meghívjuk az első eljárást, utána delay, majd a másodikat.
protected zsak is function hely return integer; function fogl return boolean; function vege return boolean; procedure huz; procedure enged (hazai : in boolean); private helyzet : integer := 5; foglalt : boolean := false; end zsak; protected body zsak is function hely return integer is begin return helyzet; end hely; function fogl return boolean is begin return foglalt; end fogl; function vege return boolean is begin if helyzet=0 or helyzet=10 then return true; else return false; end if; end vege; procedure huz is begin foglalt := true; end huz; procedure enged (hazai : in boolean) is begin foglalt:=false; if hazai then helyzet := helyzet-1; else helyzet := helyzet+1; end if; end enged; end zsak;
Fontos megkülönböztetni a hívót és a hívottat, általában a programban is az lesz a helytálló, ami a logikusabb. Pl ZH-nál a diák hívja a gyakorlatvezetőt, nem pedig fordítva. Tehát ilyenkor a gyakorlatvezető taszkjában kell legyen egy entry pont, és a body részben a hozzá tartozó accept, hogy miket hajt végre amikor ellenőrzi a feladatot. A diák taszkban pedig meg kell hívni ezt a belépési pontot. Ez teljesen ugyanúgy működik, ahogy azt az inicalizálásnál bemutattam.
Általában adatokat akarunk közvetíteni a taszkok között, ez a belépési pontokhoz rendelt diszkriminánsokkal érhető el, ami nagyon hasonlít az alprogramoknál tanultakhoz, de azért van eltérés, ugyanis belépési pontnál egy várakozási sorba kerül a hívás.
Select parancsnál nagyon fontos, hogy a hívónál és a hívottnál egész másképp működik. Ezért is nem mindegy, hogy melyik taszk melyik szerepet tölti be a randevúban.
Taszk hívásának módjai: (legyen T taszk, egy a entry ponttal, t pedig egy DURATION típusú változó)
1. Addig vár, amíg létre nem jön a randevú:
T.a;
2. Vár t idot a randevúra, de ha az addig nem következik be mást csinál:
SELECT
T.a;
OR
DELAY t;
utasítások;
END SELECT;
3. Ha nem jön létre rögtön a randevú, mást csinál:
SELECT
T.a;
ELSE
utasítások;
END SELECT;
Hívásnál a select mindkét esetben csak az említett két ágat tartalmazhatja.
Hívás fogadásának módjai: (legyen T taszk, E1, E2 és E3 entry pontokkal, t pedig egy DURATION típusú változó)
1. Addig vár, amíg létre nem jön a randevú:
accept E1;
2. Kiválasztja az egyik hívót valamelyik várakozási sorból, ha mind üres, akkor vár:
SELECT
accept E1;
OR
accept E2 do ... end E2;
OR
accept E3 ( ... ) do ... end E3;
END SELECT;
3. Mint az előző, csak ha már nem jöhet ezekre hívás, akkor a taszk terminál:
SELECT
accept E1;
OR
accept E2 do ... end E2;
OR
accept E3 ( ... ) do ... end E3;
OR
terminate;
END SELECT;
4. A feltételeket az elején egyszer megvizsgálja, és csak a nyitott ágakat (ahol a feltétel igaz volt) veszi figyelembe:
SELECT
accept E1;
OR
when feltétel => accept E2 do ... end E2;
END SELECT;
Mivel csak egyszer ellenőrzi a feltételeket, ezért itt csak olyan feltételt használjunk, amit külsőleg nem befolyásolunk, csak az itteni accept ágak során változhat meg az igazságértékük. Ha mégis erre van szükség, akkor egy kis trükkel megoldható (lásd 7. pont), de ha egy mód van rá, azért inkább kerüljük az ilyet.
5. Ha t időn belül nem jön hívás egyik entry pontra sem, akkor nem vár tovább:
SELECT
accept E1;
OR
delay t;
END SELECT;
6. Nem vár semennyit, ha azonnal nincs hívás, akkor mást csinál:
SELECT
accept E1;
ELSE
más utasítás;
END SELECT;
(7.) Ha mégis rendszeres feltételvizsgálatra van szükségünk:
LOOP
SELECT
when feltétel => accept E1;
OR / ELSE
delay t;
END SELECT;
END LOOP;
A delay előtt else van or helyett, akkor a várakozás alatt nem fogadja az esetleges hívást, azaz a két fél "elkerülheti" egymást, ha a másik fél sem túl türelmes.
Minél kisebb t, annál gyakoribb a vizsgálat, de annál jobban leterheli a processzort. Akár 0.0 is lehet, az már majdnem teljesen folyamatos, ha még az is kevés lenne, akkor a delay t; helyére kerülhet null; utasítás is, ami még sűrűbb feltételvizsgálatot eredményez, de ekkor mindenképp else kell elé.
Az or delay, or terminate ágak legfeljebb egyszer szerepelhetnek egy selecten belül, ugyanígy csak egy else ág lehet benne, minden más tetszőleges mennyiségben kombinálható.
Ha adott idő után le kell álljanak a taszkok, akkor bár a főprogram is egy taszk, mégse használható a select-nél említett módszer, hiszen nem tudunk neki entry pontot adni. Ez az időkorlát is kétféle lehet:
- mindenféleképp t ideig tart
- legfeljebb t ideig futhat, de ha már előbb végeznek a taszkok, akkor már hamarabb vége van
Elsőnél semmi ördöngősség nincs (elindulnak a taskok, jön egy delay, majd minden taskot leallitunk), második esetben viszont egy külön taszkra (legyen a neve mondjuk time) van szükségünk (legalábbis egyszerűbb megoldást nem tudok). Azt elinditjuk, miután inicializáltuk a taszkokat (pl egy init hívással), és egy select-el beállítjuk, hogy legfeljebb t ideig várjon az vege hívásra. Ezt a főprogram fogja megejteni, amikor kell neki (végeztek a taszkok, vagy mondjuk kötélhúzásnál nyert az egyik csapat, stb.). Ha az idő lejárt, akkor a time'terminated átvált true-ra, tehát ahol kell, ezt használhatjuk rá feltételnek.
procedure main is ... task time is entry init; entry vege; end time; task body time is begin accept init do null; end init; select accept vege do null; end vege; or delay 20.0; end select; end time; begin ... time.init; while(not time'terminated and not program_vege) loop -- program_vege: igaz, ha vége kell legyen (az egyszerűség kedvéért) null; end loop; if not time'terminated then time.vege; end if; ... end main;Vissza