MUTEX
alaptípus, valamint a Hoare-féle
monitorok megvalósításához könyvtári támogatás van szinkronizációs feltételek
kezelésére.
Thread
modulban gyűjtötték össze a párhuzamos programozáshoz
használt könyvtári eszközöket. A legalapvetőbb ezek közül a Closure
típus:
Ennek a típusnak a leszármazottai segítségével készíthetünk új folyamatokat. A folyamatot aTYPE Closure = OBJECT METHODS apply(): REFANY; END;
Thread.Fork
eljárás indítja el:
PROCEDURE Fork(cl: Closure): T;
Az apply
függvény a folyamat főprogramja, a Fork
ezt
futtatja le. Amit az apply
visszaad (egy REFANY
típusú
érték), az lesz a szál végeredménye. A Fork
hívás egy Thread.T
típusú értéket ad vissza, ez az elindított szál azonosítója. Ennek, valamint a
Join
eljárásnak a segítségével várhatjuk meg egy folyamat befejeződését:
PROCEDURE Join(thread: T): REFANY);
Ez az eljárás megvárja, amíg a szál főprogramja lefut, majd az általa visszaadott értékkel tér vissza.
A folyamatok közötti kommunikáció osztott változókon keresztül történhet. Ennek
a biztonságos lebonyolításához kölcsönös kizárásra van szükség. Erre több
eszközt is kitaláltak már, ebben a nyelvben mutexeket használnak,
aminek a segítségével más módszerek (pl. szemaforok) is leprogramozhatóak.
Ehhez egy MUTEX
nevű típus áll a rendelkezésünkre, amely egy
speciális objektum típus. Két állapota van: vagy szabad, vagy pontosan egy
folyamat birtokolja. Ha épp szabad, akkor bármely folyamat "magáévá teheti"; ha
már valaki birtokolja, akkor annak előbb el kell engednie, és ezután lehet újra
birtokba venni. Erre a lefoglalás-elengedés műveletpárra vezették be a LOCK
utasítást:
VAR mut: MUTEX := NEW(MUTEX); LOCK mut DO <kritikus szekció> END;
Ebben a kódrészletben a kritikus szekcióval megjelölt helyen egyszerre csak egy folyamat futhat, és mivel a LOCK utasítás be van ágyazva a blokkstruktúrába, nem fordulhat elő az a más nyelvekben gyakori hiba, hogy egy lezárt mutexet valahol elfelejtünk felszabadítani, holtpontot idézve ezzel elő.
A folyamatok futásának szinkronizálását az un. szinkronizációs feltétel ek segítségével lehet megtenni. Ez a fogalom először Hoare monitor-koncepciójában jelent meg, majd kissé módosítva több eszköztár is átvette, mivel nagyon hasznos (azaz kényelmesen és biztonságosan használható) eszköznek bizonyult. Ebben a nyelvben is lehet önállóan is használni, valamint a mutexekkel együtt monitort is könnyen lehet vele implementálni. Egy szinkronizációs feltétel lényegében várakozó szálak egy halmaza. Ehhez a halmazhoz bármelyik szál hozzáadhatja magát, vagy jelezheti, hogy a feltétel teljesült; ez utóbbi esetben a halmaz valamelyik eleme (azaz valamelyik, a feltételre váró szál) elkezd futni.
Egy ilyen feltételt a Thread.Condition
típus egy példánya
reprezentál. A feltételre a Thread.Wait(m: MUTEX, c: Condition
eljárással
lehet várakozni. Ehhez a híváshoz előbb le kell foglalni az m
mutexet;
a Wait
hívás törli a lefoglalást, vár a feltételre, majd amikor
tovább futhat, újra lefoglalja m
-et. Ez a szemantika a
monitorokban definiált wait
műveletnek felel meg.
Egy feltétel teljesülését két módon is jelezhetjük.
A Thread.Signal(c: Condition)
eljárás felébreszt egy vagy több szálat a feltételre várakozók közül (ha egyáltalán van ilyen, mert különben nem csinál semmit). A
Thread.Broadcast(c: Condition)
hívás minden várakozó szálat
felébreszt. Ezeknek a szemantikáját a könnyű implementáció érdekében
definiálták így; a használatukat általában egy őrfeltételhez szokás kötni,
ezzel együtt a nemdeterminisztikus működés ellenére jól használható eszközök.
Itt annyiban finomították ezt, hogy egy mutex segítségével pontosan meg kell adni, hogy az eljárásokon belül melyik részek nem futhatnak egyszerre. Ez a kizárás finomításán keresztül a hatékonyabb kód készítését teszi lehetővé. Az egész kódot becsomagolhatjuk egy modulba, így kívülről nem lehet elrontani a működését; ha pedig a modul által kezelt adatokat egy objektumba rakjuk össze, akkor egy nagyon szép egységet kapunk:
Egy ilyen konstrukcióban a metódusok magát az objektumpéldányt használhatják a monitoron belüli kizárásra.TYPE Monitor = MUTEX OBJECT ... END;
A szinkronizációs feltételek szemantikájának változása miatt ezeket általában
egy őrfeltételhez kötik. Ennek az őrfeltételnek a teljesülését jelzi egy szál a
Signal
hívással; ha ekkor azonban több szál is elindul, akkor lehet,
hogy nem mindnél teljesül a feltétel, ezért a Wait
hívást egy ciklusba célszerű tenni, amely vizsgálja az őrfeltételt.