Vissza a főoldalra

Segédanyag a 9. gyakorlathoz

Tartalom:


Taskok

Egy taskot úgy kell elképzelni, mintha egy önálló program lenne. Van neki saját kódja és saját memóriaterülete (Adában a taszkok elérhetik a globális változókat is, így használhatnak közös memóriaterületet, FIGYELEM ez egy veszélyes művelet!!). A taskok lényegében párhuzamosan futnak, (azaz végrehajtásuk úgy ütemeződik, hogy azt mi párhuzamosnak érzékeljük) és kommunikálhatnak is egymással. A kommunikáció úgy történik, hogy a hívó fél meghívja egy task belépési pontját, ha a hívott task ezt elfogadja akkor létrejön a kapcsolat, és egy ideig közösen futnak (azaz közös a kódjuk és ez a kód a hívott félnél van megírva). Két taszk kommunikációját randevúnak hívják. Egy task nem önálló fordítási egység, lehet őket több fileba tenni, de mi az egyszerűség kedvéért minden taszkot a főprogram deklrációs részében definiáljunk.

Task létrehozása - specifikációs rész

TASK <tasknév> IS 
    ENTRY <belépési_pont_név> (<paraméterek>); 
    ... 
END <tasknév>; 
 
Ha egy tasknak nincs belépési pontja akkor elég az alábbi specifikáció
 
TASK <tasknév>; 

Task létrehozása - törzsrész

TASK BODY <tasknév> IS 
    [DEKLARÁCIÓS RÉSZ] 
BEGIN 
    [UTASÍTÁSOK] 
END <tasknév>; 
 
Ha a törzsrészt külön fileba akarjut tenni, akkor ide a köv. dolgot kell írni:
 
TASK BODY <tasknév> IS SEPARATE
 
FONTOS!! A specifikációs résznek mindig meg kell előzni a törzsrészt, és minden más olyan task törzsrészét is ahol hivatkozunk rá.
 

Task típusok

Előfordulhat, hogy egy taszkból több példányt is szeretnénk létrehozni, (pl. közlekedés szimulációban minden gyalogosnak ugyanaz a kódja) ekkor használjuk a task típust. Így egyszer definiálunk egy típus működését, majd tetszőleges számú példányt tudunk belőle létrehozni. A taszk típust diszkriminánsal is elláthatjuk. A specifikációs rész szintakszisa a következő:
 
TASK TYPE <tasktípusnév>[(<diszkriminánsnév_1> : <diszktét_típus_1>[; ... [;<diszkriminánsnév_n> : <diszktét_típus_n]])] IS 
    ENTRY <belépési_pont_név> (<paraméterek>); 
    ... 
END <tasknév>; 
 
Természetesen itt sem kötelező belépési pontokat megadni. Ekkor a kód a következő:
 
TASK TYPE <tasktípusnév>[(<diszkriminánsnév_1> : <diszktét_típus_1>[; ... [;<diszkriminánsnév_n> : <diszktét_típus_n]])]; 
 
A törzsrész szintakszisa megegyezik a sima taszkéval
 
Ezek azonban még csak típusok, ezekből létre kell hozni a konkrét taszkokat.
Ennek két módja van, egyik a deklarációs részben hozzuk létre
 
<konkrét_task> : <tasktípusnév>; 
 
Vagy akár tömböt is csinálhatunk belőle, így annyi taszkunk lesz, ahány elemű a tömb
 
<konkrét_tasktömb> : ARRAY(1..n) OF <tasktípusnév>; 
 
A másik mód, hogy létrehozunk tasktípusra mutató mutatót, majd a főprogramban vagy valamelyik taskban dinamikusan hozunk létre új taskot:
 
TYPE <taskmutatótípus> IS ACCESS <tasktípusnév>; 
<taskmutató> : <taskmutatótípus>; 
 
BEGIN 
... 
<taskmutató> := NEW <tasktípusnév>; 
 

Taskok elindulása és terminálása

Azok a taskok amelyek onállóak (azaz nem egy task típusból hoztuk őket létre), illetve azok a task típusból létrehozott taskok amelyeket a deklarációs részben hoztuk létre akkor indulnak el, amikor a vezérlés elérkezik az őket tartalmazó deklarációs rész BEGIN-jéhez. Amelyeket dinamikusan hoztuk létre, azok a new utasitás kiadásaokr indulnak.
Egy taszk kompletté válik (azaz még létezik, de már nem fut) amikor eljut az utasításai végére, majd terminál, ha minden tőle függésben levő taszk kompletté vált. Kompletté válhat ha terminate utasításhoz ér, vagy kívülről aborttal leállították.
 

Task attribútumok

tasknév'callablehamis, ha a task komplett
tasknév'terminatedigaz, ha a task terminált
belépési_pont'Countvisszaadja, hogy hány taszk várakozik ennél a belépési pontnál. Ezzel csak a task saját belépési pontjait kérdezheti le.
 

Belépési pontok / hívás fogadás

A belépési pontoknál tud csatlakozni a hívó taszk a fogadóhoz. A belépési pontot a taszk specifiációs részében is fel kell tüntetni:
 
ENTRY <belépési_pont_név> [(<paraméter_1> : [IN | OUT | IN OUT] <típus_1>[; ... [<paraméter_n> : [IN | OUT | IN OUT] <típus_n>] ])]; 
 
A paraméter módjai:
INAdatot vár a hívótól
OUTAdatot szolgáltat a hívónak
IN OUTAdatot vár és szolgáltat
 

Hívás elfogadása / belépési pontok a törzsrészban

1. A hívott fél vezérlése addig várakozik egy belépési pontnál, amíg azt valaki meg nem hívja. Ilyenkor a vezérlés akkor sem ugrik át más belépési ponthoz, ha ott már várakoznak.

 
ACCEPT <belépési_pont_név>(<paraméterlista>) DO 
    <utasítások> 
END <belépési_pont_név>; 
 
 

2. Több belépési pont közül arra ugrik a vezérlés, ahol hívás van. Ha egyszerre több belépési ponthoz érkezik hívás, akkor nem determinisztikusan választ egyet közülük.

 
SELECT 
    ACCEPT <belépési_pont_név_1>(<paraméterlista>) DO 
        <utasítások> 
    END <belépési_pont_név_1>; 
OR 
    ACCEPT <belépési_pont_név_2>(<paraméterlista>) DO 
        <utasítások> 
    END <belépési_pont_név_2>; 



OR 
    ACCEPT <belépési_pont_név_n>(<paraméterlista>) DO 
        <utasítások> 
    END <belépési_pont_név_n>; 
[OR 
    TERMINATE;] 
END SELECT
 
A TERMINATE ág akkor hajtódik végre, ha már az összes olyan taszk kompletté vált, amelyik a SELECT-ben szereplő belépési pontok valamelyikét hívhatná. Ez arra jó, hogy ha van egy kiszolgáló taszkunk, akkor ezt a SELECT blokkot egy végtelen ciklusba tehetjük, és ha az összes taszk ide vonatkozó taszk kompletté válik, akkor a kiszolgáló taszk eljut a TERMINATE utasításhoz és ő is terminál. Így egyszerűen helyetteni tudunk, egy bonyolult kilépési feltételt.
 
Lehetőségünk van őrfeltételeket is rendelni a belépési pontokhoz. Ez azt jelenti, hogy a taszk mielőtt elfogadná egy belépési ponthoz értkező hívást, megnézi, hogy az őrfeltétele igaz-e. Ha igaz, elfogadja a hívást, ha hamis, akkor pedig úgy tekinti, hogy ez az ág nem szerepel a SELECT-ben. Őrfeltételt csak a SELECT struktúrában tudunk alkalmazni. (Könnyen meggondolható, hogy az előző esetben nincs értelme az őrfeltételnek (az igaz őrfeltételnek nincs hatása, a hamis pedig „lefagyasztaná" a taszkot).)
 
SELECT 
    [WHEN <feltétel_1>] ACCEPT <belépési_pont_név_1>(<paraméterlista>) DO 
        <utasítások> 
    END <belépési_pont_név_1>; 
OR 
    [WHEN <feltétel_2>] ACCEPT <belépési_pont_név_2>(<paraméterlista>) DO 
        <utasítások> 
    END <belépési_pont_név_2>; 



OR 
    [WHEN <feltétel_n>] ACCEPT <belépési_pont_név_n>(<paraméterlista>) DO 
        <utasítások> 
    END <belépési_pont_név_n>; 
[OR 
    TERMINATE;] 
END SELECT
 
 

3. Ha nem akarunk várakozni egy belépési pontnál, akkor azt a következő módon tehetjük meg:

 
SELECT 
    ACCEPT <belépési_pont_név>(<paraméterlista>) DO 
        <utasítások> 
    END <belépési_pont_név>; 
ELSE 
    <utasítások> 
END SELECT
 
Tehát itt először megnézzük, hogy vár-e taszk a belépési pontnál, ha igen akkor elfogadjuk a hívását, ha nem vár ott senki akkor pedig az ELSE ágban leírt programrész fog lefutni.
 

Hívás

A hívásnak is három módja van:

1. Addig vár a hívó, amíg hívását el nem fogadják:

<tasknév>.<belépési_pont_név>[(<paraméterek>)]; 
 

2. Megnézi, hogy rögtön elfogadják-e a hívását, ha igen létrejön a kapcsolat, ha nem akkor mást csinál

SELECT 
    <tasknév>.<belépési_pont_név>[(<paraméterek>)]; 
ELSE 
    <utasítások> 
END SELECT
 

3. Megnézi, hogy egy adott időn belül elfogadják-e a hívását, ha igen létrejön a kapcsolat, ha nem akkor mást csinál

SELECT 
    <tasknév>.<belépési_pont_név>[(<paraméterek>)]; 
OR 
    DELAY <türelmi_idő_másodpercben>; 
    <utasítások> 
END SELECT
 
A DELAY DURATION típusú értéket vár. (A 1.0 átkonvertálódik duration típussá, de ha a f float típusú, akkor a delay f nem megengedett.)
A DELAY utasítást önmagában is ki lehet adni, akkor azt jelenti, hogy a vezérlés a paraméterben kapott másodpercig vár.
 

Példaprogramok

 
 
Vissza a főoldalra