A BETA programozási nyelv

Párhuzamosság



Párhuzamosság

Egy BETA objektum alapja lehet egy végrehajtási szálnak. Ha ezt akarjuk, az objektumot speciális módon kell deklarálni:

A: @ | Activity;

Ezzel egy új objektum jön létre az Activity mintából, melyre az A változó egy statikus referencia lesz, ami párhuzamosan futhat a többi szállal. Egy szálat lehet futtatni konkurens folyamatként, vagy akár korutinként is. Például:

Activity: (# do cycle (# do GetOrder; suspend ProcessOrder; suspend; DeliverOrder; suspend; #) #);

A szálat az A parancs segítségével lehet elindítani, ami elindítja a do részben levő programot. A futás ideiglenesen felfüggesztődik, mikor kiad egy suspend utasítást. Ezután egy újabb A parancs kiadásával onnan folytatódik a szál futása, ahol abbamaradt. Így ha B szintén egy példány az Activity mintából, akkor a következő parancs a két szál között váltogat:

cycle(# do A; B; #);

Ezzel a módszerrel mint determinisztikus korutinokat futtathatjuk a szálakat. A hívó objektum vezérli az egyes korutinok végrehajtását.

Komponensek egyidejű végrehajtása

Komponensek egyidejű végrehajtására a fork imperatív szolgál:

S.fork

ahol S egy referencia. Az S.fork jelentése az, hogy az S végrehajtása az S-et végrehajtó komponens végrehajtásával egyidejűleg történik. Az S végrehajtása mindaddig folyamatban van, míg nem hív egy suspendet, vagy S futása be nem fejeződik. Ha S végrehajtásának felfüggesztését egy explicit suspend okozta, akkor folytatható egy újabb S.fork hívással.

Egyszerű szinkronizáció

Egyszerű szinkronizációt szemaforokkal valósíthatunk meg. A BETA nyelv rendelkezik beépített semaphore mintával. A semaphore-nak két művelete van: P és V. Használata:

(# S: @semaphore; A: @ | (# do imp1; S.P; imp2; S.V; imp3 #); B: @ | (# do imp4; S.P; imp5; S.V; imp6 #); do S.V; A.fork; B.fork #)

Az alábbi példa bankszámlák kezelésén keresztül mutatja be a szemafor használatát. Az egyenleghez (balance) a betét (Deposit) és a kivét (Withdraw) csak egymást kizárva férhetnek hozzá.

Account: (# mutex: @Semaphore; {semaphore controlling access} balance: @integer; Deposit: (# amount,bal: @integer enter amount do mutex.P; balance+amount->balance->bal; mutex.V exit bal #); Withdraw (# amount,bal: @integer enter amount do mutex.P; balance-amount->balance->bal mutex.V exit bal #); Init:< (#do mutex.V; {Initially open} INNER #) #)

Monitorok

A bonyolultabb szinkronizációk segítésére a BETA-ban definiálták a monitor mintát.

Monitor: (# mutex: @Semaphore; Entry: (# do mutex.P; INNER; mutex.V #); Init:< (#do mutex.V; INNER #) #)

A monitor mintának egy szemafor attribútuma és egy Entry lokális eljárása van, melyek közül minden pillanatban legfeljebb csak egy futhat. A Monitor mintának a wait kiterjesztése arra szolgál, hogy az eljárásokban meg tudjuk várni bizonyos feltételek teljesülését:

wait(#do <some condition>->cond #)

Az alábbi példa a termelő-fogyasztó feladat egy lehetséges megvalósítása. A termelő a billentyűzetről olvas be karaktereket egy bufferbe, a fogyasztó pedig a bufferből kivett karaktereket írja ki a képernyőre.

ORIGIN '~beta/basiclib/systemenv'; ---program: descriptor--- SystemEnv (# buffer: @Monitor (# R: [20] @char; in,out: @integer; full,empty: @Condition; put: Entry (# ch: @char enter ch do (if in = out then full.wait if); ch->R[in]; (in mod R.range)+1 ->in; empty.signal; #); get: Entry (# ch: @char do (if in = (out mod R.range)+1 then empty.wait if); R[(out mod R.range)+1->out]->ch; full.signal; exit ch #); init::< (# do 1->in; R.range->out #) #); prod: @| System(# do cycle(# do keyboard.get->buffer.put #) #); cons: @| System(# do cycle(# do buffer.get->screen.put #) #); do buffer.init; conc(# do prod[]->start; cons[]->start #) #)

Összetett rendszerek

Összetett rendszernek nevezzük a több részből álló többeseményes rendszereket. Összetett rendszerek létrehozására a System minta conc attribútuma szolgál. A párhuzamosan futtatandó mintáknak a System mintát kell kiterjeszteniük, majd ezeket a "start" függvény meghívásával kell aktiválni. Például:

S: @ | system (# S1: @ | system(# ... do ... #); S2: @ | system(# ... do ... #); S3: @ | system(# ... do ... #); ... do conc(#do S1.start; S2.start; S3.start #); ...

Ekkor S meghívásával az Si objektumok do utáni része párhuzamosan kezd futni, és csak akkor lép tovább, ha az összes Si befejezte a futását.

Randevúk

A BETA könyvtár nyújt támogatást az Ada-ban használt szinkronizált randevúk megvalósítására is. A következő példa egy italautomata szimulációját végzi el. Az automata kávét és levest tud adni, a vevő az automatát a "make_coffee" vagy "make_soup" műveleteivel indíthatja el. Ha a "make_coffee" eljárást hívták meg, akkor a "get_coffee" művelettel lehet elkérni a kész kávét, a levest pedig a "get_soup" művelettel. A "System" mintának van egy "port" nevű mintája, amivel a szinkronizációt meg lehet valósítani. A "port" minta definiál egy "entry" nevű műveletet. A "port" típusú objektum "entry" műveletének felülírásával lehet kapcsolatba hozni egy "port"-ot és egy műveletet.

Az automata programja:

DrinkMachine: System(# activate: @port; coffee_ready, soup_ready: @port; drink_ready: @port; make_coffee: activate.entry(# do ... coffee_ready[]->drink_ready[] #); make_soup: activate.entry(# do ... soup_ready[]->drink_ready[] #); get_coffee: coffee_ready.entry(# do ... exit some_coffee[] #); get_soup: soup_ready.entry(# do ... exit some_soup[] #); do cycle(# activate.accept; drink_ready.accept; #) #);

Az "activate.accept" utasítás addig várakozik, amíg egy másik szál nem hívja meg az "activate" port-tal kapcsolatba hozott valamelyik műveletet. Kezdetben az automata elfogadja a "make_coffee" és a "make_soup" hívásokat, de ennek a végrehajtása után már csak a megfelelő "get_..." művelet hajtódhat végre, a nem megengedett műveleteket hívó szálaknak várakozniuk kell. A megfelelő "get_..." művelet lefutása után az automata ismét elfogadja a "make_..." műveleteket. Az automatát a következő módon lehet használni:

machine: @|DrinkMachine; ... machine[]->fork; machine.make_coffee; machine.get_coffee; machine.make_soup; machine.get_soup; ...

A BETÁ-ban tehát a "System", "port", "entry" minták lehetővé teszik az Ada-ban megszokott task-szinkronizációt igénylő feladatok megoldását.

Nemdeterminisztikus végrehajtás

A fejezet elején láttunk példát arra, hogyan lehet folyamatokat alternálva végrehajtani. Az ott bemutatott módszer determinisztikus, ám a BETÁ-nak van eszköze arra, hogy ezt nemdeterminisztikusan tegyük: a System minta alt attribútuma. Példa:

System (# C1: @ | System(# ... SUSPEND ... #); C2: @ | System(# ... SUSPEND ... #); C3: @ | System(# ... SUSPEND ... #) do alt(# do C1.start; C2.start; C3.start #) ...

Azok a komponensek, amelyek éppen nem futnak, a végrehajtási folyamatuk egy jól definiált pontján várakoznak. Ezek azok a pontok, ahol a végrehajtás átadódik valamelyik másik komponensnek. Ezek a pontok a következők lehetnek:

A kommunikáció itt azt jelenti, hogy egy komponens egy másik komponens egy entry eljárását vagy egy acceptet akar végrehajtani.

Példa konkurenciára, monitorra és szemaforra: Billentyűzetbuffer