Az Eiffel programozási nyelv

Párhuzamosság

Eiffel-ben a párhuzamosságot csupán a legutóbbi, 4-es verzióban vezették be, tehát eredetileg nem kifejezetten párhuzamos programozáshoz tervezték a nyelvet. Ennek ellenére a rájuk jellemző egyszerűséggel és eleganciával oldották meg ezt a bonyolultnak látszó kérdést.

Mint később látni fogjuk, az Eiffel-beli párhuzamosság nem kötődik semmiféle előre definiált párhuzamos architektúrához. Saját belső felépítéséhez igazodik inkább, így nem korlátozzák alkalmazásait ilyen jellegű előfeltételek. Tetszőlegesen leképezhető a fizikai architektúrára, és megvalósító vele a párhuzamosság bármelyik formája (multithreading, kliens-szerver, elosztott programozás, stb).

Eiffel-ben a párhuzamosság megvalósításához elegendő volt egyetlen új kulcsszó bevezetése: ez a separate. A párhuzamosság többi részét tulajdonképpen a már korábban is létező nyelvi elemek szemantikájának megváltoztatásával valósították meg. A separate egy minősítő kulcsszó, mellyel egy osztályt illethetünk definiáláskor (a class kulcsszó előtt), vagy egy különálló objektumot, deklarációjakor. (Ebben az értelemben használata hasonlít az expanded kulcsszó alkalmazásaira.) Ebből az is következik, hogy Eiffel-ben az objektumon belüli párhuzamosság nem valósítható meg. (Ld. még OOP.)

Példa:

separate class PHILOSOPHER ... x: separate FORK;


A szeparált objektumok konstruktora szintaktikailag semmiben sem különbözik a hagyományos objektumokétól, szemantikailag azonban többet jelent. Minden szeparált objektum létrehozásakor egy új ,,processzor'' is létrejön, ami az objektum eljárásait futtatni fogja. Ez nem feltétlenül egy fizikai processzor. A konkurens programozás tetszőleges egységéhez hozzárendelhető, legyen az akár egy processz az operációs rendszerben, egy thread egy multithreaded rendszerben, stb.

Szinkronizáció

Minden párhuzamos környezetben alapvető szükség van a szinkronizáció problémáinak megoldására. Eiffel-ben ezt a kérdést is nagyon egyszerűen oldották meg.

left, right: separate FORK; ... eat(left, right); ...


Ha egy eljárás paramétereként szeparált objektumok szerepelnek, akkor ezekre automatikusan kölcsönös kizárás valósul meg, azaz egyidőben két művelet nem hajtható végre rajtuk.

supplier.operation(arguments);


Ha egy szeparált objektum eljárását hívjuk, akkor az aszinkron módon hajtódik végre, azaz elkezdődik az objektumhoz tartozó processzoron a metódus végrehajtása, de a hívó eljárás futása nem függesztődik fel.

value := supplier.some_query(...);


Ha egy szeparált objektum függvényét hívjuk (vagy egy attribútumára hivatkozunk), akkor a hívó fél futása felfüggesztődik, mert a függvény végeredményét illetve az attribútum értékét meg kell várnia. A szinkron és az aszinkron hívások ilyetén keveredésével valósítható meg az, hogy a hívó félnek csak akkor kelljen várakoznia a hívott fél miatt, amikor arra feltétlenül szükség van. Ez az implicit szinkronizáció nem igényel semmiféle plusz munkát a programozó részéről.

Feltételes metódushívásra is szükség lehet, vagyis arra, hogy egy adott művelet kizárólag bizonyos feltételek teljesülése esetén hajtódjon végre. Erre a szekvenciális Eiffel-ből már ismert előfeltételes technika alkalmazása tökéletesen megfelel.

Példa:

oldest(q: BUFFER [T]): T is require not q.empty do Result:= .... end


Abban az esetben, ha a BUFFER osztály separate-ként volt definiálva, a require rész nem helyességbizonyítási eszközként szolgál, hanem a várakozási feltétel szerepét tölti be: az oldest függvényt hívó kliens addig fog várakozni, amíg a feltételként megadott állítás igazzá nem válik (és a q objektum fel nem oldódik egy esetleges korábbi kölcsönös kizárás alól).

Annak egyébként sem lenne túl sok értelme, ha a require utáni feltételt helyesség szempontjából vizsgálnánk, hiszen a hívó fél a párhuzamos környezet miatt nem tudna felelni a teljesítéséért (a feltétel vizsgálata és a rutin hívása között eltelt időben akár meg is változhat az objektum állapota). Így az egyetlen értelmes megoldás csak az lehet, ha a megadott állítást belépési feltételnek tekintjük, melynek teljesülnie kell a művelet végrehajtása előtt. (Ld. még helyességbizonyítás.)

Prioritások

Ritka esetben szükség lehet arra, hogy egy fontos (VIP) kliens elvegye egy kölcsönös kizárással kezelt objektum elérését az azt éppen használó másik objektumtól. Ezt persze nem lehet minden jelzés nélkül megtenni, hiszen ez a módszer inkonzisztens állapotba hozhatja a résztvevő objektumokat. Ezért erre azt a megoldást dolgozták ki az Eiffel fejlesztői, hogy az eredeti hívóban kivételt váltanak ki, ezzel jelezve azt, hogy megfelelően korrigálja jelenlegi állapotát. Erre külön könyvtári osztályt biztosít a konkurens Eiffel. Természetesen arra is lehetőség van, hogy a korábbi hívó ne adja át a kezelt objektum elérését. Ekkor az elérést átvenni akaró objektumban váltódik ki a kérdéses kivétel. Egy kevésbé agresszív megoldás az, hogy ha a második hívó nem tudja megszerezni az elérést, akkor várakozó állapotba kerül, és kivárja, amíg az első hívó befejezi tevékenységét, és megszünteti a kezelt objektum zároltságát.

A demand utasítás szolgál a zár elkérésének bejelentésére, a yield a zár átengedésére, az insist pedig várakozásos elkérés bejelentésére.

Implementáció

Az Eiffel-ben megírt logikailag párhuzamos programok fizikai párhuzamos eszközökre való leképzéséhez egy új file-típust vezettek be a nyelv fejlesztői: ez a Concurrency Configuration File (CCF). Ez a file nem tartozik közvetlenül a párhuzamos programhoz, és különböző rendszerekben különbözőképpen képezheti le ugyanazt a konkurens Eiffel-ben írt szoftvert. Az aktuális leképzési mód futás közben is lekérdezhető és módosítható a párhuzamos programon belül.