Tanácsok a taszkos feladatokhoz (ZH-hoz és beadandóhoz egyaránt)

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)

Taszkok inicializálása

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;

Taszkok közti kommunikáció

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ó.

Időkorlátos főprogram

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