Párhuzamos programozást támogató nyelvi eszközök összehasonlítása: Nyelvek, eszközök: Ada 95
2.5
Ada 95
A US DoD által megalkotott Ada nyelv új verziója, melynek
szabványát 1995-ben fogadták el.
2.5.1
Bevezetõ az eszközrõl
Az Ada nyelvet már tervezésekor
felkészítették párhuzamos feladatok
megoldására. Ez az átgondolt tervezés,
többszörösen ellenõrzött
változtatásokkal a nyelv további
fejlõdésében is érezhetõ.
Az Ada95 az 1983-ban definiált szabványtól alapvetõen nem tér el, de nagyon sok területen javítottak rajta. Az eltelt 12 évben sok kívánalom merült fel a felhasználások során. Ezeket a kívánalmakat, kérdéseket több fórumon megvitatták és így alakították ki a nyelv 1995-ös változatát.
Az új verzió nagyon sok területen hozott újdonságokat:
Az Ada95 összes újdonságának bemutatása
meghaladná e dolgozat terjedelmét, ezért itt csak a
párhuzamos programozást érintõ
részekrõl lesz szó. Az Ada83 egyes részeit is meg
kell itt említeni, mert néhány dolog egy kicsit
megváltozott, illetve tisztázódott azóta.
2.5.2
Felhasználói felület
Egy futási szálat egy task objektum valósít meg, melynek lehetnek entry pontjai is, ahol más taszkokkal szinkronizálódhat és kommunikálhat (randevúzhat).
A task objektumot egy specifikációs, illetve egy törzs részre lehet bontani. A specifikációs részben adhatóak meg a taszk belépési, avagy entry pontjai:
task PRODUCER; task buffer is entry PUT(C: in character); entry GET(C: out character); end buffer;
A taszk törzsében a lokális változókat és a viselkedést meghatározó kódot adjuk meg:
task body PRODUCER is C: character; begin loop PRODUCE(C); buffer.PUT(C); end loop; end PRODUCER;
Az így létrehozott taszk típusok limited private típusok, ezért sem az értékadás, sem az összehasonlítás nem megengedett rájuk.
Taszkok rugalmasabb kezeléséhez a task objektumot típusként érdemes deklarálni, így hivatkozás típust is lehet hozzá készíteni, amely persze már átadható egy másik alprogramnak is.
Ha a hivatkozás használata nem elképzelhetõ -és a program nem akarja meghívni a taszk egy entry pontját sem -, akkor felhasználható a taszk egyértelmû azonosítására a Task_ID is. Ez egy private típus, amelyet a task objektum ismeretében meg lehet határozni.
A Task_ID-t alacsony szintû folyamatkezelésre (pl. ütemezések, prioritások beállítására) és speciális problémák megoldására (pl. egy olyan protokoll implementálására, ahol a folyamatnak vissza kell emlékezni, hogy ki hívta õt utoljára) lehet felhasználni. Az ilyen problémák közös vonása az, hogy több típusú taszkra is hivatkozni kell, amit egyetlen hivatkozás típussal nem lehet megoldani.
Folyamatot a taszk objektumot deklaráló programrész végrehajtásával lehet elindítani. Ha az objektum deklarációs részben van definiálva, akkor a hozzá tartozó folyamat a deklarációs részt követõ törzs elsõ utasítása elõtt elindul. Ha hivatkozás típussal van meghatározva, akkor az allokátor végrehajtásakor indul el a folyamat.
Ha az indítás során hiba lép fel, akkor a taszk komplett állapotba kerül, és egy TASKING_ERROR kivétel lép fel.
A folyamatok mindig függnek valamilyen szülõ folyamattól - deklarációs egység folyamatától, vagy típusdeklarációs egységtõl hivatkozási típusoknál -, ezért mindig hierarchiába rendezhetõek. Ebben a hierachiában a függõségi viszonyok tranzitívan átadódnak.
Ha egy taszk futása leáll - kivétel esetén a kivételkezelõ véget ér -, akkor az kompletté válik. Ha egy folyamat komplett és az összes tõle függõ folyamat már terminált, akkor maga is terminál és felszabadulnak az általa elfoglalt erõforrások. Egyébként a folyamat pontosan akkor terminál ha végrehajtása egy terminate utasításhoz ér, olyan szülõtõl függ, ami már terminált és a szülõtõl függõ összes folyamat terminált má, vagy szintén egy terminate utasításnál várakozik. (Tehát ha terminate utasításhoz ér, és már egyetlen társ-folyamat sem randevúzhat vele, akkor megáll.)
Taszkokról a következõ attributumokat lehet lekérdezni:
Entry hívások és accept utasítások szolgálnak a taszkok közötti szinkronizációra és kommunikációra.
Egy task objektum definiálásakor lehet specifikálni belépési pontokat - melyek szerkezete az alprogram-deklarációknak megfelelõ -, melyeket a folyamat futása során más taszkok meghívhatnak.
A taszk törzsében accept utasításokkal fogadhat entry hívásokat. Az accept utasítások szerkezete alprogramok szerkezetének felel meg (az Ada95-ben már kivételkezelõ rész is lehet benne). Az utasításhoz tartozó blokkban azok a mûveletek szerepelnek, melyeket akkor kell elvégezni, amikor az accept utasítás egy entry hívást fogad.
Ha az adott folyamat egy accept utasításnál tart, és egy másik folyamat az ehhez tartozó entry pontot hívja, akkor a két folyamat szinkronizálódik. A hívó folyamat felfüggesztõdik, a hívott átveszi az entry pontnál meghatározott paramétereket, végrehajtja az accept utasításhoz tartozó blokkot, majd az out típusú paramétereket beállítva tovább engedi futni a hívó folyamatot. Ez a kapcsolat a randevú, melyben szinkronizálódnak a folyamatok és szinkron kommunikációval cserélnek ki adatokat.
Ha egy adott belépési pontot több folyamat hív, vagy a hívott folyamat nem tart éppen a belépési ponthoz tartozó accept utasításnál, akkor a hívó olyamat felfüggesztõdik, és a hozzá tartozó várakozási sorba kerül. Ebbõl a sorból normál esetben érkezési sorrendben kerülnek ki a folyamatok, de ez a viselkedés prioritásos rendszerekben megváltoztatható.
task body buffer is BUF : character; begin loop accept PUT(C : in character) do BUF := C; end PUT; accept GET(C : out character) do C := BUF; end GET; end loop; end buffer;
Belépési pontokat számozással is el lehet látni, és így entry családokat is létre lehet hozni, amely nem pritoritásos rendszerekben is lehetõséget ad a hívó folyamatok közötti különbségtételre.
Belépési pontokról a következõ információkat lehet lekérdezni:
Az adott entry-hez tartozó accept végrehajtása elõtt a folyamat semmilyen információt nem szerezhet arról, hogy ki hívja õt, és milyen paraméterekkel, tehát a randevú egy aszimmetrikus kommunikációs forma.
Delay utasítással egy folyamatot egy idõre fel lehet függeszteni:
delay 3.5; -- 3.5 masodpercig varAz utasításban az idõt másodpercekben kell megadni.
Ha egy megadott idõpontig kell várakoztatni a folyamatot, akkor azt az Ada83-ban a következõ módon lehetett leírni:
delay Elerendo_Ido - Clock;Ennek a szerkezetnek az a veszélye, hogy a folyamat végrehajtása a Clock meghívása után egy idõre felfüggesztõdhet - egy másik folyamat futása miatt -, ezzel a várakozási idõ érvénytelen értékre állítódhat be. Ennek a problémának a megoldására már egy elérendõ idõt is lehet definiálni az Ada95-ben, nem csak várakozási idõt:
delay until Elerendo_Ido;
Abort utasítással egy futó folyamatot abnormal állapotba lehet hozni. Az ilyen állapotban lévõ folyamat addig futhat, amíg egy accept, delay, select utasításhoz, vagy egy entry híváshoz nem ér, és ekkor komplett állapotba kerül és befejezi futását.
Ez az utasítás lényegében egy másik folyamat megállítására szolgál, de lehetõséget ad a másik folyamatnak, hogy rendezetten álljon meg.
Az Ada95 kiegészítései között szerepel megoldás egy folyamat azonnali (azaz amilyen hamar csak lehet) megszakítására is.
Task objektumoknak az Ada95-ben már lehet diszkriminánsa, mellyel sok folyamat párhuzamos indításakor el lehet kerülni az entry hívásokkal való inicializálásból származó szûk keresztmetszetet:
function Next_One return Task_Range; ... task type Computer(Index: Task_Range := Next_One); The_Tasks: array (Task_Range) of Computer;Task objektumok bõvíthetõ típusokon belül is szerepelhetnek, illetve azokkal is paraméterezhetõek, így nagy rugalmasságot biztosíthatnak objektum orientált rendszerek számára. Ezekben az esetekben a bõvíthetõ típus tartalmazhatja az adatrészt, és a rajtuk elvégezhetõ mûveleteket, a hozzá kapcsolódó folyamat pedig a szinkronizációt.
A select utasítás az Ada95-ben három szerepkörben is elõkerül:
task body buffer is ... begin loop select when LENGTH > 0 => accept GET(C: out character) ... end GET; or when LENGTH < MAXIMUM => accept PUT(C: in character) ... end PUT; or terminate end select; end loop; end buffer;
A select utasításban a lehetséges végrehajtási ágakat az or kulcsszavak választják el. Ezen ágakban a when kulcsszóval egy feltétel is bevezethetõ, amely az ág végrehajtását engedélyezheti, vagy letilthatja (a feltételben csak a task objektum belsõ állapotára lehet hivatkozni, az entry pontnál várakozó folyamat attribútumaira, illetve paramétereire nem).
Az ágak között lehet terminate, illetve delay ág is. Ezen ágakra csak akkor kerül a vezérlés, ha accept utasítást tartalmazó ágak nem választódhatnak ki.
A select utasításban szerepelhet egy else ág is, melyre akkor kerül a vezérlés, ha minden más ág nem választódhat ki (az else ág és a delay, terminate ágak kizárják egymást).
A szelektív várakoztatás a végrehajtható ágak között nem definiált módon választ.
select buffer.PUT(C); -- ha eppen var ra egy accept, -- akkor vegrehajtodik else null; end select; select buffer.PUT(C); -- 5 masodpercig var az elfogadasra or delay 5.0; end select;
select
delay 5.0; -- kontroll blokk
Put_Line("Sajnos kifutott az idobol...");
then abort -- vegrehajtando blokk
Iszonyu_Nagy_Matrix_Invertalasa(A);
end select;
A
kontroll blokkban bármilyen utasítások szerepelhetnek.
Pl.: a kontroll blokk tartalmazhat egy
függvényhívást, amely egy speciális
billentyût figyel, amivel félbe lehet szakítani a
végrehajtandó blokkban folyó bármilyen
mûveletet.
Az Ada83-ban bármilyen szinkronizációra egy új folyamatot kellett létrehozni, amely törzsében elrejtve védhette a közösen használt adatokat. Ez a módszer egyszerûbb szinkronizációs eseteknél (pl. kölcsönös kizárás) elég költséges megoldásnak bizonyult, ezért bevezették a Concurrent Pascal monitorjához hasonló osztott adattípust.
Osztott adattípust a protected kulcsszóval lehet megadni. Osztott adattípus specifikációs részében lehet megadni az adattípus adattagjait, és az adattípuson végezhetõ mûveleteket:
protected type Bounded_Buffer is
entry Put(X: in Item);
entry Get(X: out Item);
function Full return BOOLEAN;
private
A: Item_Array(1 .. Max);
I, J: Integer range 1 .. Max := 1;
Count: Integer range 0 .. Max := 0;
end Bounded_Buffer;
Az
osztott adattípus törzsében lehet leírni a
hozzá tartozó mûveletek
megvalósítását:
protected body Bounded_Buffer is
entry Put(X: in Item) when Count < Max is
begin
A(I) := X;
I := I mod Max + 1; Count := Count + 1;
end Put;
entry Get(X: out Item) when Count > 0 is
begin
X := A(J);
J := J mod Max + 1; Count := Count - 1;
end Get;
function Full return BOOLEAN is
begin
return Count < Max;
end Full;
end Bounded_Buffer;
Az osztott adattípus egy passzív szerkezet, azaz - a task objektumoktól eltérõen - nincs önállóan végrehajtódó része. Minden rajta végzett mûvelet az azt hívó folyamat végrehajtási menetébe illeszkedõen fut le.
A folyamatokhoz hasonló az ütközések kezelése (procedure vagy entry többszörös hívásánál), itt is várakozási sorok tartoznak a mûveletekhez, melybe bekerülve az adott folyamat futása felfüggesztõdik, és csak akkor folytatódik, amikor az kerül sorra.
Egyes esetekben - egy entry hívás csak késõbbi idõpontban, vagy csak részben teljesíthetõ - szükség lehet arra, hogy a hívó folyamatot rávegye a hívott alprogram, hogy újra hívja õt, vagy egy másik alprogramot. Ebben az esetben segíthet a requeue utasítás, amely nem engedi vissza a vezérlést a hívó folyamatnak, hanem átteszi azt a paraméterként megadott várakozási sorba:
if Full then requeue PUT(C); end if;
Ez a direktíva az Ada95-bõl az említett gyengeségek miatt kimaradt, és helyette két új direktíva használható:
type Data is new Long_Float; pragma Atomic(Data); -- tipusra alkalmazva I: Integer; pragma Volatile(I); -- valtozora alkalmazvaCsak a hardware által támogatott méretû típusokra lehet megkövetelni a típusmódosítók támogatását:
Buffer: array (1 .. Max) of Integer; pragma Atomic_Components(Buffer);A konstans objektum csak olvasható objektumot jelöl, nem azt, hogy ennek értéke külsõ behatásra nem változhat meg:
type IO_Rec_Type is
record
Start_Address: System.Address;
pragma Volatile(Start_Address);
...
end record;
IO_Rec: constant IO_Rec_Type;
for IO_Rec'Address use ... ;
Ezen törekvések célja, hogy az Ada fordító által generált program jobban illeszkedjen a futtató rendszer operációs rendszeréhez, illetve hardware-éhez, ezáltal hatékonyabban oldhasson meg feladatokat.
Az egyes pontokban leírt megoldások eredményeképpen szinte minden lényeges ütemezõ rész lecserélhetõ az Ada programban, ezáltal az adott feladathoz ideálisan igazodó program hozható létre.
A valós idejû rendszerekkel kapcsolatban megfogalmazott követelmények mindig implementáció függõek, ezért részletesebb vizsgálatukat egy adott rendszeren érdemes elvégezni.
Ha Ada programot osztott rendszeren, vagy MPP architektúrán kell futtatni, akkor ezt az elosztást a programozónak kell megtennie. Ehhez az Osztott rendszerekre vonatkozó kiegészítés ad segítséget, illetve szabványosít egyes lépéseket.
A következõ követelményeket tûzték ki a nyelv kiterjesztésekor:
Egy normális Ada program - akár több folyamat is lehet benne - egyetlen memóriaterületen fut. Egy ilyen programot egy partíciónak hívnak. Osztott rendszereken több program-partíciót is létre lehet hozni, melyeket egy-egy fizikai processzorhoz hozzá lehet rendelni.
Az Ada95-ben könyvtári egységek szintjén definiálni lehet, hogy az egység egy adott particionálásban milyen szerepet játszhat:
package A is -- egyik aktiv particioban
pragma Remote_Call_Interface(A);
procedure P( ... );
...
end A;
--------------
with A;
package B is -- masik aktiv particioban
...
A.P( ... ); -- tavoli eljarashivas lesz a
... -- normalis eljarashivasbol
end B;
A rendszer az Ada nyelv konzisztenciája miatt továbbra is egységes marad, hiszen a távoli eljáráshívások típusellenõrzései a könyvtári egységek közötti típusellenõrzésekre vezethetõek vissza.
Egy távoli eljáráshívásban a hívó folyamat felfüggesztõdik, és az eljárás befejezõdésekor visszakapja a vezérlést. Ebbõl a szempontból a távoli eljáráshívások a lokális eljáráshívásokhoz hasonlóan mûködnek (a fordító egy partíción belüli távoli eljáráshívást lokális eljáráshívássá is optimalizálhat). A távoli eljáráshívásokhoz semmilyen szinkronizációs kód nem kapcsolódik, akár több példány is futhat ugyanabból az eljárásból.
Ha ez a viselkedés a kommunikációs réteg lassúsága miatt nem megfelelõ, vagy nincs szükség a végeredmény ellenõrzésére, illetve kivételek lekezelésére, akkor aszinkron módon is végrehajtható a távoli eljáráshívás:
package A is
pragma Remote_Call_Interface(A);
procedure P( ... );
pragma Asynchronous(P);
...
end A;
Az így definiált alprogram csak eljárás lehet, és csak in típusú paraméterei lehetnek. Az aszinkron módon meghívott távoli eljárás lefutását nem várja meg a hívó folyamat, az indítás után továbbmegy. A hívott eljárás a távoli partíción lefut, de nem ad vissza semmilyen jelzést az eredményességrõl a hívó folyamatnak.
A fent leírt módon specifikált package-eket külön-külön le lehet fordítani. A particionálást csak az összeszerkesztés lépésénél kell megtenni, de ennek részleteit az Ada95 nyelv nem definiálja, csak ajánlásokat ad.
A particionált program futtatásáról - dinamikus, vagy statikus folyamat modell használ - az Ada95 szintén nem nyilatkozik, ezt az adott implementációra bízza.
A partíciók közötti kommunikáció
elérése érdekében egy elõre definiált
felületû kommunikációs package-t kell
megvalósítani az adott architektúrán. Ez a
package (System.RPC) egy távoli
eljáráshívás protokollját, adatok
becsomagolásának és kinyerésének
módját specifikálja. Távoli
eljáráshívásokhoz szükséges rutinokat a
legtöbb osztott rendszer már operációs rendszer
szintjén támogatja, ezért ezek
megvalósítása nem jelenthet technikai
problémát.
2.5.3
Példák
-- mandel_gen.ads package Mandel_Gen is -- e miatt mas processzoron is lehetne pragma Remote_Call_Interface(Mandel_Gen); procedure calculate(x1, y1, x2, y2 : in FLOAT; width, height : in INTEGER; pix : out STRING); procedure write_pix(filename, pix : in STRING; width, height : in INTEGER); end Mandel_Gen; -- mandel_gen.adb with TEXT_IO; use TEXT_IO; package body Mandel_Gen is procedure calculate(x1, y1, x2, y2 : in FLOAT; width, height : in INTEGER; pix : out STRING) is x, y : FLOAT; -- re, im coords. ar, ai : FLOAT; a1, a2 : FLOAT; ite : CHARACTER; xs, ys : FLOAT; begin xs := x2 - x1; ys := y2 - y1; for iy in 0 .. height-1 loop y := (FLOAT(iy) * ys) / FLOAT(height) + y1; for ix in 0 .. width-1 loop x := (FLOAT(ix) * xs) / FLOAT(width) + x1; ar := x; ai := y; ite := CHARACTER'FIRST; begin for iteration in CHARACTER loop a1 := ar * ar; a2 := ai * ai; exit when a1 + a2 > 4.0; ai := 2.0 * ai * ar + y; ar := a1 - a2 + x; ite := CHARACTER'SUCC(ite); end loop; exception when CONSTRAINT_ERROR => ite := CHARACTER'LAST; end; pix(iy * width + ix + pix'FIRST) := ite; end loop; end loop; end calculate; procedure write_pix(filename, pix : in STRING; width, height : in INTEGER) is package INT_IO is new INTEGER_IO(INTEGER); use INT_IO; F : FILE_TYPE; begin Create(F, OUT_FILE, filename); SET_LINE_LENGTH(F, UNBOUNDED); SET_PAGE_LENGTH(F, UNBOUNDED); PUT_LINE(F,"P5"); PUT_LINE(F,"# Mandelbrot set"); PUT(F, width, 3); PUT(F," "); PUT(F, height, 3); NEW_LINE(F); PUT_LINE(F,"255"); PUT(F,pix); Close(F); exception when others => Close(F); raise; end write_pix; end Mandel_Gen;
Sajnos nem volt elérhetõ osztott rendszerre kódot gyártó fordító, ezért csak a következõ tesztet tudtam kipróbálni:
-- mandel_dist.adb
with Mandel_Gen; use Mandel_Gen;
procedure mandel_dist is
width : constant INTEGER := 512;
height : constant INTEGER := 512;
hosts_num: constant INTEGER := 4;
pix : STRING(1 .. width*height);
x1, y1 : FLOAT;
x2, y2 : FLOAT;
delta_y : FLOAT; -- computed slice size
step_y : FLOAT;
dy, sy : INTEGER;-- pix slice sizes
y_1, y_2: FLOAT;
begin
x1 := -2.0; y1 := -2.0;
x2 := 2.0; y2 := 2.0;
delta_y := (y2 - y1) / FLOAT(hosts_num);
dy := height / hosts_num;
if dy = 0 then
dy := 1;
end if;
for h in 0 .. hosts_num-1 loop
if h = hosts_num-1 then -- last slice is different
sy := height - h * dy;
y_1 := y1 + FLOAT(h) * delta_y;
y_2 := y2;
else
sy := dy;
y_1 := y1 + FLOAT(h) * delta_y;
y_2 := y1 + FLOAT(h+1) * delta_y;
end if;
calculate(x1, y_1, x2, y_2, width, sy,
pix(h*dy*width+1 .. (h*dy+sy)*width));
end loop;
write_pix("mandel_ada.pgm", pix, width, height);
end mandel_dist;
-- prodcons.adb
with Text_IO; use Text_IO;
with Ada.Integer_Text_IO; use Ada.Integer_Text_IO;
procedure prodcons is
--
-- The shared queue type
--
const MAX : integer := 12;
protected Queue is
entry Put(X: in INTEGER);
entry Get(X: out INTEGER);
private
subtype Index is INTEGER range 0 .. MAX;
queueA: array (Index) of INTEGER;
head : Index := 0;
tail : Index := 0;
Full : Boolean := False;
Empty : Boolean := True;
end Queue;
protected body Queue is
entry Put(X: in INTEGER) when not Full is
begin
queueA(head) := X;
head := (head + 1) mod MAX;
if head = tail then
PUT("The queue is full.");
Full := True;
end if;
Empty := False;
end Put;
entry Get(X: out INTEGER) when Full is
begin
X := queueA(tail);
tail := (tail + 1) mod MAX;
if head = tail then
PUT("The queue is empty.");
Empty := True;
end if;
Full := False;
end Get;
end Queue;
--
-- Producer task
--
task type Producer(Q: access Queue;
Work_length : POSITIVE := 10);
subtype ItemRange is INTEGER range 1 .. 100;
package Items is new Ada.Numerics.Discrete_Random(ItemRange); use Items;
task body Producer is
G : Generator;
number : INTEGER;
begin
number := Random(G);
for I in 1 .. Work_length loop
PUT("producing: "); PUT(number);
Q.Put(number);
number := number + 1;
end loop;
end Producer;
--
-- Consumer task
--
task type Consumer(Q: access Queue;
Work_length : POSITIVE := 10);
task body Consumer is
number : INTEGER;
begin
for I in 1 .. Work_length loop
Q.Get(number);
PUT("consuming: "); PUT(number);
end loop;
end Consumer;
-- the main part
Q : aliased Queue;
P : array (1 .. 2) of Producer(Q'Access);
C : array (1 .. 2) of Consumer(Q'Access);
begin
PUT("Producer consumer problem is running...");
end prodcons;
A nyelvben meglévõ eszközök nagyon bonyolult problémák megoldására is alkalmasak, tehát nehezen párhuzamosítható, kiegyensúlyozatlan feladatok elvégzésére, illetve speciális részfeladatok alapján történõ munkamegosztás támogatására is felhasználhatóak. A nyelv nem támogatja az adatpárhuzamosságot és nem kínál kész megoldásokat nagyléptékû matematikai problémák kezelésére, mint az HPF.
A megbízhatóság és a biztonságos programozás támogatása az Ada nyelv szerves része, és ebbõl a követelménybõl a párhuzamosság támogatásával sem vesztett, ezért ez a nyelv alkalmas nagy megbízhatóságú feladatok megvalósítására is.
Az Ada nyelv folyamat-párhuzamosságot támogat, de osztott rendszerekre vonatkozó kiterjesztésével ez több önálló programpartícióra, lényegében több önálló programra terjesztõdik ki.
Egy partíción belül a folyamatok dinamikus modelljét implementálja, programparticiók szintjén viszont az adott rendszerre bízza a partíciók statikus, vagy dinamikus voltának kérdését.
Egy partíción belül az Ada95 nyelv a folyamatok közötti kommunikáció és szinkronizáció eszközeinek széles választékát nyújtja - közös és osztott változók, taszkok, randevúk -, partíciók között ez az osztott változók és távoli eljáráshívások használatára szûkül le.
Az Ada95 legtöbb párhuzamosságot támogató eszköze ötvözi kommunikációs és szinkronizációs elemeket:
Az Ada95 nyelv a következõ tulajdonságokkal rendelkezik:
Az Ada95 nyelv tehát sok olyan dolgot valósít meg, amelyek nagy megbízhatóságú és rugalmas párhuzamos programok írását teszik lehetõvé, de ezen területen a publikusan elérhetõ változatokban csak a folyamatok párhuzamossága tesztelhetõ.
|
|
|
|