Az OCCAM programozási nyelv

Párhuzamosság

A processz fogalma

A processzek általában független entitások, melyek egy párhuzamos rendszer különböző ágenseit reprezentálják. Függetlenek olyan értelemben, hogy mindegyik tartalmazza a megfelelő adatokat saját feladatának végrehajtásához. Elvárjuk továbbá tőlük, hogy teljesítsék a modularitás következő három szabályát:

Az OCCAM esetében egy processz más processzek kompozíciója, primitív processzekből egyre összetettebb processzek épülnek. Általában a következőképp néz ki egy processz:

A <Declarations> részben a processz által használt változókat deklaráljuk, a <Process.Body> rész a processz végrehajtható részét definiálja.

A legegyszerűbb processz egy programban a tevékenység (action), ami lehet értékadás, input és output.

Egyszerű processzek

A CCS és CSP féle nyelveknél az egyszerű (primitív) processzek mint egyszerű műveletek, processzek által elvégezhető események vannak jelen. Ezek atomi műveletek, az előző processz felfogás szerint igazán nem is processzek.

SKIP

A SKIP egy nullprocessz. Elindul, nem csinál semmit, majd terminál. Különböző fajta strukturált eljárások írásakor hasznos.

STOP

A STOP is nullprocessz, mint a SKIP, de szemben a SKIP-pel, a STOP egy "elromlott" processzt eredményez. Hasznos akkor, ha egy hiba fellépése után ezzel állítunk meg egy processzt, így megakadályozva a hiba esetleges továbbterjedését.

Értékadás

Ez a process hasonló, mint más nyelvek értékadó utasításai, de az OCCAM-ban lehetőségünk van párhuzamos értékadásra is, vagyis több változóhoz egy időben rendelni hozzá értékeket. Pl.:

x, y := y, x :

Ekkor x és y értéke kicserélődik. A párhuzamos értékadások mindig ugyanarra az eredményre vezetnek, mintha a jobb oldali kifejezéseket sorra kiértékelnénk, majd ezután hozzárendelnénk rendre a bal oldali változókhoz. A kifejezések és változók száma meg kell, hogy egyezzen, tehát pl. a

x, y :=10 :

értékadás hibás.

Input/Output

Két kommunikációra használt primitív processz áll rendelkezésre.

Összetett processzek

Nagyobb processzeket az egyszerű processzekből készíthetünk adott konstrukciós eszközökkel, melyek a következőek lehetnek:

SEQ

 szekvencia

IF

 feltétel

CASE

 szelekció

PAR

 párhuzamos végrehajtás

ALT

 alternation

Szekvencia

A szekvenciában az egyik processz befejezése után indul a következő. A szekvencia áll a SEQ kulcsszóból és nulla vagy több processz felsorolásából, két szóköznyi bekezdéssel.
Pl.: Ez két szekvenciálisan végrehajtott processzben bekér egy karaktert a keyboard csatornáról majd kiírja azt a screen csatornára.

SEQ
   keyboard ? char
   screen ! char

A konstrukciók egymásba ágyazhatók, mint pl.:

SEQ
    SEQ
       screen ! char
       ...
    SEQ
       ...

Ismételt szekvencia

Ismételt szekvenciát használhatunk, ha indítani akarunk megegyező processzeket szekvenciálisan, és indításkor ismerjük a processzek számát. Pl.:

SEQ i = 0 FOR array.size
    stream ! data.array[i]

Feltétel

A feltétel tetszőleges számú processzből és hozzájuk tartozó feltételekből áll. Végrehajtáskor sorban megy végig a feltételeken, és amely feltétel igaz, ahhoz tartozó processzt végrehajtja, majd terminál. Ha egyik feltétel sem igaz, akkor a feltétel működése megegyezik a STOP elemi processzével. Pl.:

IF
    x < y
        x := x + 1
    x >= y
       SKIP

Ismételt feltétel

A feltételt ugyanúgy lehet ismételni, mint a szekvenciát. Pl.: (két sztring összehasonlítása)

IF
   IF i = 1 FOR hossz
       sztring[i] <> objekt[i]
          talalat := FALSE
    TRUE
       talalat := TRUE

Szelekció

Adott egy kifejezés majd felsorolva a szelektorok, a szelektorokhoz processzek rendelve. Amelyik szelektor értéket a kifejezés felveszi, az az ág hajtódik végre, ha nincs ilyen, működése megegyezik a STOP-éval. Lehetőség van ELSE ág megadására, amely akkor hajtódik végre, ha egyik más ág sem. Pl.:

CASE betu
    'e' , 'u' , 'i' , 'o' , 'a'
       maganhangzo := true
    ELSE
       maganhangzo := false

WHILE ciklus

A while ciklus addig ismétel egy processzt, míg a feltétele igaz. Pl.:

WHILE buffer <> eof
    SEQ
       in ? buffer
       out ! buffer

Párhuzamos végrehajtás

A PAR szerkezet valahány processzt tartalmaz, melyek végrehajtása párhuzamosan történik. Áll a PAR kulcsszóból, ,majd a processzek listájából, két szóközzel bentebb kezdve. A párhuzamosítás beágyazható önmagába, így létre lehet hozni a processzek egy hiearchikus szerkezetét. Pl.:

PAR
   editor
    PAR
       keyboard
       screen

Megszorítások változók és csatornák párhuzamos használatára (a kölcsönös kizárás biztosítására):

Ismételt párhuzamosítás

A párhuzamosítást ugyanúgy ismételtethetjük, mint a szekvenciát és a feltételt. Pl.:

PAR FOR i = 3 TO 4
    user [i] ! message

Alternatív végrehajtás

Az alternatív végrehajtás tetszőleges számú processzből áll, melyekhez tartozik egy-egy őrfeltétel. Ezek közül csak egy hajtódik végre, valamelyik azok közül, ahol az őrfeltétel igaz, hogy melyik, azt a nyelv nem definiálja. Az őrfeltételben használható a csatornára való írás és csatornáról olvasás művelete. Ez akkor lesz igaz értékű, ha a művelet sikeres. Ha nincs egy ilyen ág sem az alternációban, akkor a végrehajtás szünetel addig, amíg valamelyik igaz nem lesz. & jellel lehet összekapcsolni több őrfeltételt.

Pl.: Itt a csatornáról olvasás nem is történik meg, ha left.enabled hamis.

ALT
    left.enabled & left ? packet
       stream ! packet
    right ? packet
       stream ! packet

Ismételt alternatív végrehajtás

Hasonlóképp, mint az előzőek.

Prioritásos alternatív végrehajtás

Lehetőségünk van prioritást rendelni az alternatív végrehajtás processzeihez a PRI ALT kulcsszóval. Ekkor a forrásban felvett sorrendjüknek megfelelően csökken a prioritásuk. Amennyiben több processz őrfeltétele is igaz, a nagyobb prioritású (előbb lévő) hajtódik végre. Pl.:

PRI ALT
    disk ? block
       d ()
    keyboard ? char
       k ()

Kommunikáció

Az OCCAM prcesszek változókkal működnek, csatornák és timer-ek (időzítők) segítségével kommunikálnak. A változó rendelkezik egy értékkel, új értéket kaphat egy értékadásból vagy egy csatornáról mint input. A csatornák értékeket közvetítenek. Az időzítők előállítanak egy értéket, mely az időt reprezentálják.

Csatorna típus

A kommunikációs csatorna valamely értékek nem pufferelt, point-to-point szállítását teszi lehetővé konkurens processzek közt. Ezen értékek formátumát és típusát az ún. protokoll írja le. A csatorna neve és a protokoll alkotják a csatorna deklarációját. A CHAN OF kulcsszót követi a protokoll. A csatorna típust nem lehet elnevezni, a protokollt viszont igen.

A csatorna típusa:

channel.type = CHAN OF protocol

A csatornákat hasonlóképp deklaráljuk mint a változókat, még tömböket is képezhetünk belőlük, használhatjuk a komponenseket és szegmenseket is.

Protokollok:

Protokollokat külön elnevezhetünk a következőképp:

PROTOCOL name IS protocol :

Egyszerű protokollok:

A legegyszerűbb protokollok csak egy típusnévből állnak. Pl.:

CHAN OF [36] BYTE message : CHAN OF COMPLEX imp :

Számolt hosszú tömb protokoll:

Ezzel lehetőségünk nyílik előre nem ismert hosszúságú tömbök kommunikálására a csatornán keresztül. Megkapjuk a tömb méretét és méretnyi elemet. Pl.:

CHAN OF INT : : [] BYTE message :

message ! 16 :: "itt egy 16 hosszu szoveg"
message ? len :: [buffer FROM start]

Szekvenciális protokoll:

A szekvenciális protokoll egyszerű protokollok kommunikációjának egymásutánja. Pl.:

PROTOCOL COMPLEX IS REAL32; REAL32
items ? real.part ; imaginary part

CASE protokoll:

A CASE protokoll lehetővé teszi különböző formájú üzenetek kommunikációját egy csatornán kereszül. Pl.:

PROTOCOL FILES
    CASE
       request ; BYTE
       filename ; DOSFNAME
       word ; INT16
       record ; INT32 ; INT16 :: []BYTE
       halt
       :

CHAN OF FILES dfs :

dfs ! request ; get.record
dfs ! halt

dfs ? CASE
    record ; rnumber :: rlen :: buffer
       ...
    word ; number
       ...

Anarchikus protokoll:

Néha szükséges lehet, hogy olyan csatornát definiáljunk, ahol valamilyen oknál fogva a protokoll formátuma nem ismert. Erre jó az anarchikus protokoll. Erre használható az ANY kulcsszó. Pl.:

CHAN OF ANY mouse :
PROTOCOL PRN IS ANY :
CHAN OF PRN printer :

Ebben az esetben a csatornára csak írni és olvasni lehet, nem használható összetett protokoll. A küldött és fogadott adatok mint bájtsorozat kezeljük. Csatornára írást a ! jel, olvasást a ? jel szolgálja.

Távoli eljáráshívás (hívható csatornák)

Az Occam 3-ban lehetőségünk van távolról hívható csatornatípus (remote call channel) definiálására. Ezeknek segítségével érhetünk el egy másik gépen futó szolgáltatást, adatbázist vagy egyebet. A távoli eljáráshíváshoz a két gép között point-to-point kapcsolatra van szülkség. Alapvetően ugyanúgy működik, mint egy helyi eljáráshívás, de több megkötés és szintaktikai különbség van köztük:

Kétféle paramétere lehet:

Távoli eljárást deklarálni kell a hívó félnél (pontosabban: hívható csatorna deklarálása): Pl.:

CALL cosine (RESULT REAL32 result, VAL REAL32 x) :

A szerverben pedig definiálni kell a hívást fogadó eljárást: Pl.:

ACCEPT cosine (RESULT REAL32 result, VAL REAL32 x)
SEQ
    calls := calls+1
    result := COS (x)

És a hívása pedig ugyanúgy történik, ahogy a normál eljáráshívás: Pl.:

cosine (cos.pi, 3.14159(REAL32))

Osztott cstornák (Occam 3)

Osztott hívható csatornák

Osztott hívható csatornát hasonlóan lehet deklarálni, mint a szokásos csatornákat, csak egy SHARED kulcsszó kell elé. Pl.:

SHARED CALL cosine (RESULT REAL32 result, VAL REAL32 x):

Az így deklarált hívható csatornát meg lehet osztani több processz között. A megosztott csatorna használat megegyezik a meg nem osztottéval. A fogadó processz nem fogad el többet egy hívásnál ugyanabban az időben.

Osztott kommunikációs csatornák

Osztott kommunikációs csatornát hasonlóan lehet deklarálni, mint a szokásos csatornákat, csak egy SHARED kulcsszó kell elé. Pl:

CHAN TYPE RPC
    RECORD
       CHAN OF REAL32 param, result:
:
SHARED RPC sine :

Az osztott csatorna használata: ahhoz, hogy hozzá tudjunk férni egy osztott kommunikációs csatornához az elosztott végén, előbb hozzáférési jogot kell hozzá igényelnünk a CLAIM kulcsszóval. Ezek után a CLAIM blokkon belül ugyanúgy használhatjuk a csatornát, mintha nem lenne osztott. Pl.:

CLAIM sine
   SEQ
      sine[param] ! 3.14159(REAL32)
      sine[result] ? x

Ha a nem megosztott végét akarjuk használni a csatornának, akkor ezt a végét a GRANT kulcsszóval ki kell sajátítani a processzünk számára, és ezek után ezt a csatornát már rajtunk kívül senki más nem használhatja.

GRANT sine
   REAL32 y:
   SEQ
      sine[param] ? y
      sine[result] ! SIN (y)

Osztott hívható csatornákat is lehet Osztott kommunikációs csatornákkal szimulálni (Lásd előző példa).

Szinkronizációs eszközök

A nyelv szinkronizációs eszközöket nem szolgáltat, lévén csak az üzenetküldés párhuzamos programozási modellt támogatja. A processzek szinkronizációja a kommunikációval és a timer-eken keresztül valósul meg. Ez azt jelenti, hogy a csatornát író processz blokkolódik addig, amíg az írást nem tudja elvégezni (még van a csatornán elem), az olvasó processz pedig addig, amíg olvasni nem tud (nincs rajta elem).

Időzítések

Timer

A timer egy speciális csatorna, ahonnan a rendszeróra olvasható le. A timert mint változót deklaráljuk, majd az óra értékének fogadására egy integer változót használunk. Példa:

TIMER clock:
INT time:
clock ? time

Az AFTER kifejezés használatával lehetőségünk nyílik timeout processzek írására. Példa:

clock ? AFTER time

Ekkor a futtató process végrehajtása felfüggesztődik addig, míg a clock rendszeridő a time utánira nem nő. A kifejezésnek az AFTER után egészértékűnek kell lennie.

Interface-ek és modulok (Occam 3)

Interface-ek

Occam 3-ban lehetőségünk van interface-eket definiálni, és ezáltal elrejteni a reprezentációt a felhasználók elől. Ez a funkció alapvetően a távolról hívható csatornákra épül. Pl.:

INTERFACE
    CALL Get (RESULT BYTE c, VAL INT n) :
    CALL Put (VAL INT n, VAL BYTE c) :
TO
    [block.size]BYTE cache:
    INITIAL
       from.disk.block ? cache
    :
    FINAL
       to.disk.block ! cache
    :
    SERVER
    ...
:
:

Itt deklaráltunk két hívható csatornát a Put-ot és a Get-et mint interface-t, amelyek implementálják a cache-t.

Modul típus

Az előző pontban láthattuk, hogy egy logikai egységhez hogyan rendelhetünk hozzá egy korlátozott interface-t. Ebben a pontban megmutatjuk, hogy hogyan tudunk definiálni több elnevezett modul-t hasonló implementációval. Pl.:

MODULE TYPE cache
    INTERFACE
       CALL Get (RESULT BYTE c, VAL INT n) :
       CALL Put (VAL INT n, VAL BYTE c) :
    TO
       [block.size]BYTE cache:
       INITIAL
          from.disk.block ? cache
       :
       FINAL
          to.disk.block ! cache
       :
      SERVER
          ...
      :
    :
:

És ebből a modul típusból tudunk létrehozni példányokat. Pl.:

MODULE cache1 IS INSTANCE cache (to.disk.block, from.disk.block) :

Vagy akár tömbbe is pakolhatjuk a létrehozott modulokat:

MODULE caches IS [INSTANCE i = 0 FOR 10 :
CACHE (to.disk.block[i],from.disk.block[i])] :

Ez a deklaráció ekvivalens a következőkkel:

MODULE caches[0] IS CACHE (to.disk.block[0],from.disk.block[0]) :
MODULE caches[1] IS CACHE (to.disk.block[1],from.disk.block[1]) :
...
MODULE caches[9] IS CACHE (to.disk.block[9],from.disk.block[9]) :

Itt minden egyes tömbelemnek ugyanaz az interface-e, noha nem ugyanaz a típusuk.

Occam programok futása egy és több processzoron

Több processzoron történő futtatás

A párhuzamos komponensek mindegyike egy egyedi processzoron fut. A nyelv ebben az esetben lehetőséget ad rá, hogy mi döntsük el, hogy melyik processz melyik processzoron fusson. Ennek megadására használható a PLACED kulcsszó. Pl.:

PLACED PAR
    PROCESSOR 1
       terminal (term.in, term.out)
    PROCESSOR 2
       editor (term.in, term.out, files.in, files.out)
    PROCESSOR 3
       network (files.in, files.out)

A processzorok megadása számozással történhet, mint ahogy ez a példában is látszik.

Egy processzoron történő futtatás

Ha csak egy processzor áll rendelkezésünkre, akkor is tudjuk futtatni az ekkor kvázi párhuzamos programjainkat, és ehhez kapunk egy újabb nyelvi elemet, amellyel meghatározhatjuk a processzek prioritását. A prioritást a processzek sorrendje alapján állítjuk fel, és a legelső processz-nek lesz a legnagyobb a prioritása. Az egymást követő processzek egyre csökkenő prioritást kapnak, és a legutolsó processz pedig csak kerül végrehajtásra, ha az összes előtte lévő folyamat már befejeződött. Ennek megadására a PRI kulcsszó. Pl.:

PRI PAR i = 0 FOR 8
   users (term.in[i], term.out[i])