A Pool programozási nyelv

Bevezetés

Általában

A Pool egy objektum-orientált, párhuzamos programozásra alkalmas nyelv, amely nem megosztott memóriával rendelkező párhuzamos gépekre lett fejlesztve. Egy ilyen gép volt a 100-node Doom (Decentralized Object-Oriented Machine) amelyen már futtatni is lehetett a Pool-ban írt programokat. A POOL nyelvcsalád része a POOL, POOL2, POOL-I, POOL-T (valamint nem párhuzamás környezetre az SPOOL).

Párhuzamosság

Az "üzenetküldés" terminológiája ellenére a legtöbb OOP nyelv szekvenciális, azaz az alábbi 3 megszorításnak eleget tesz:

  • 1. A futás pontosan egy objektum aktivizálásával kezdődik.
  • 2. Mikor az objektum üzenetet küld, az üzenet eredményének megérkezéséig nem csinál semmit.
  • 3. Egy objektum csak akkor aktív amikor egy bejövő üzenetre felelve egy metódust futtat.


  • A párhuzamosság bevezetésének több módja lehet:

  • 1. elhagyása. A processz fogalmának bevezetése. Több processz lehet aktív és minden processz egy-egy OOP programot futtat. Ezek a processzek ugyanazon az objektumhalmazon dolgoznak. Az is lehetséges, hogy többen hajtsák végre ugyanazon objektum ugyanazon metódusát. így még mindig nem megoldott, és külön lehetőségeket kíván a szinkronizáció és a kölcsönös kizárás. (Ilyen pl. a Smalltalk-80 párhuzamos kiegészítése.


  • 2. elhagyása: aszinkron kommunikáció. Az üzenetküldő párhuzamosan fut az címzettel. Mivel sok üzenetváltás történik, nagyfokú párhuzamosság érhető el.


  • 3. elhagyása. Az objektum nem csak vár az üzenetek érkezésére, hanem saját feladata van, amit törzsnek nevezünk. Az objektum létrehozásával elkezdődik a törzs végrehajtása a többi objektumtörzzsel párhuzamosan. A törzs pontosan meghatározott pontjain megszakítható azért, hogy válaszoljon az üzenetre. Ez randevú formájában történik. A küldő és fogadó szinkronizálódik (aki kommunikálni akar, vár míg a másik hajlandó lesz), a paramétereket átadják, feldolgozzák, majd az eredményt visszaadják (nem feltétlenül a metódus-végrehajtás végén), utána párhuzamosan futnak tovább. Ebben a megközelítésben is nagyfokú párhuzamosság érhető el megfelelő számú objektum létrehozásával. A POOL család nyelvei törzseket használnak a párhuzamosság fő kifejezési eszközeként.
  • A különböző megközelítések összehasonlítása

    Először olyan szempontból vizsgáljuk meg a fenti megközelítéseket, hogy mennyire segítik megoldani a párhuzamosság problémáját. A párhuzamos programozás lelke a processzek egyenlőtlen végrehajtásából adódó nemdeterminisztikusság kezelése: ezt a minimumra kellene szorítani, de egy bizonyos mennyiségnek maradnia kell, hogy hatékonyan kihasználja a párhuzamosságot. Ez a nemdeterminisztikusság nagyon gyorsan növekszik a processzek és azon belül az atomi utasítások számának növelésével. Az első megközelítés hátrányai nyilvánvalóak: a processz végrehajtásának minden pillanatában várhat interakciót (azaz inkább megszakítást). Külön mechanizmusok szükségesek a nemdeterminisztikusság csökkentésére (pl. szemaforok). Ezek fegyelmezett használatot igényelnek, melyet nem kényszerít ki a nyelv. Ezek miatt ez a megközelítés nem alkalmas nagyméretű párhuzamos programokhoz.
    A párhuzamos programozás osztott változókkal és üzenetküldéssel megvalósított formája. Az osztott változókkal dolgozás nehézségei rögtön kiderülnek, mihelyst összevetjük az üzenetküldéssel. Ez az osztott változós programok ellenőrzésének formalizációjakor rögtön kiderül. Az ilyen programok formális ellenőrzésének klasszikus rendszerei szerint invariáns állítást kell adnunk minden processz minden más processzel való kapcsolata előtt és után. Például n processz és m kapcsolattal és m+1 állítással n*(m+1)*m*(n-1) ellenőrzést igényel. Az n csökkentése a párhuzamosságot csökkenti, míg m csökkentése megoldható az atomi eljárások méretének növelésével. Pontosan ez történik üzenetküldés során. Sőt, mivel a partnert gyakran jelzik explicit, ez az ellenőrzést leszorítja egypár résztvevő kommunikációs állításra, ami jóval kevesebb ellenőrzést igényel.

    Természetesen az osztott változók és az üzenetküldés közti választást befolyásolja a gép architektúrája. Osztott memóriával rendelkező rendszerekben és szekvenciális rendszerekben az osztott változók implementálása triviális. Másrészről az osztott memória nélküli, kommunikációs hálózattal összekapcsolt processzorok esetén az üzenetküldés rendszere illeszkedik az architektúrához. Ilyen gépen bár lehet, de nagyon nehéz és nem hatékony osztott változókat implementálni. Tehát, ha nem ismert az architektúra, akkor jobb az üzenetküldést választani. Az eddigiek mutatják, hogy az objektumok és a párhuzamosítás összeegyeztetésekor a másik két megközelítés célszerűbb. (tehát az aszinkron kommunikáció vagy a törzsek) Itt az objektum és a processz fogalma eggyé válik, és ezek a szavak szinonimák lesznek. A processzek csak jóldefiniált helyeken kapcsolódhatnak, sőt a kapcsolat módja is csak a paraméterek és az eredmények küldésére korlátozódik. Az objektum változói más objektumoktól védve vannak. Ha adatokat kell több processz közt megosztani, akkor egy külön objektumba rakjuk őket, s így elérésük is jóldefiniált lesz a metódusokon keresztül. Az objektumon belül minden szekvenciálisan történik, azaz a szekvenciális belső elkülönül a párhuzamos külsőtől. Ha megengednénk több párhuzamos processzt is egy objektumon belül, az csak elrontaná ezt a kedvező helyzetet.

    Az aszinkron kommunikáció és a törzsek közti döntés már nem olyan nyilvánvaló, mint az előző esetben. A szinkronizáció hiánya nem csak a rendszer rugalmasságát növeli meg, kihasználva az erőforrásait, de felvet néhány problémát is: Sokkal többféle futása lehel, mint szinkron kommunikáció esetében, és a programozónak kell biztosítani, hogy mind helyes eredményt ad. A másik probléma a már elküldött, de még meg nem érkezett üzenetek pufferelése. Elvben nem használhatunk rögzített pufferméretet blokkolva a küldőt, ha több üzenetet próbálna küldeni, mint a pufferméret, mert ez holtpontot idézhet elő a szemantikailag helyes programban is. Például ha a küldő n darab 'a' üzenetet küld aztán egy 'b'-t míg a fogadó egy 'b'-t vár először, akkor holtpont alakul ki, ha n nagyobb, mint a pufferméret.
    Egy másik probléma, hogy hogyan garantáljuk, hogy ugyanazon küldő és fogadó közt az üzenetek érkezési sorrendje megegyezzen a küldési sorrendjükkel. Ez biztosítható, ha protokollokkal vagy fix rutinokkal látjuk el a hálózat minden kapcsolódó csúcsát, és ügyelünk, hogy a sorrend az átvitel minden pontjában megmaradjon. Ez azonban lassítja a programot. Megjegyezzük, hogy könnyű aszinkron kommunikációt implementálni olyan nyelven ami csak szinkron kommunikációt és törzset tartalmaz: minden aszinkron üzenetnek egy puffer objektumot kell létrehozni. A POOL-ban szinkron kommunikációt és törzset választottunk a párhuzamosság kifejezéseként. Ha a programozó nem ír törzset, egy alapértelmezett törzset használ a rendszer, ami folyamatosan válaszol a beérkezett utasításokra. Egy metódus még a befejezése előtt visszaadhat eredményt a küldőnek. Az üzenetek átvételére az answer utasítás szolgál. Az utasítás egy metódusnév listát is tartalmaz. Azaz pontosan egy üzenet válaszolódik meg, az első olyan, ami a listában szereplő metódusnevekre hivatkozik. A POOL2 feltételes answer utasítást is tartalmaz, ami specifikálja, hogy egy üzenet csak akkor válaszolódjon meg, ha egy feltételnek megfelel. Ha nincs ilyen üzenet, a feltételes answer utasítás azonmód terminál. Ez az Ada-szerű select utasítás általánosítása.

    A POOL2-ben az aszinkron kommunikáció is támogatott. Ez úgy tekinthető, mintha egy puffer objektum keletkezne minden üzenethez, azaz az üzenetek sorrendje nem biztosított. Összehasonlítva a POOL-t a hagyományos párhuzamos nyelvekkel, egyszerűen besorolhatók a következők szerint:

  • 1. Osztott változó, vagy üzenetküldés (POOL: üzenetküldés)
  • 2. Paraméter-érték átvitele, vagy távoli eljárás hívás (POOL: távoli eljárás hívás)
  • 3. Szinkron vagy aszinkron kommunikáció (POOL2: mindkettő)
  • 4. Csatornák, vagy a kommunikációs partner direkt megnevezése (POOL: direkt megnevezés)
  • 5. Statikus vagy dinamikus processz struktúra (POOL: dinamikus)


  • Legtöbb választás követi azt az igényt, hogy minél kevesebbet változtassunk a szekvenciális OOP-n. A többi választást már feljebb tárgyaltuk. Végül egypár szót szólunk a fairségről (fairness). Két feltétel teljesülésével már biztosítható bizonyos fairség. Az első az, hogy a végrehajtás "sebessége" tetszőleges, de pozitív. Másszóval mikor egy objektum végrehajtása folytatódhat, azaz nem vár üzenetre, vagy üzenet végrehajtására, akkor folytatódik a végrehajtása. A POOL program végrehajtásának második feltétele hogy az objektumok által küldött üzenetek egy sorban legyenek tárolva, azért hogy megőrizzék az érkezési sorrendet. Mikor az objektum végrehajt egy answer utasítást akkor az első megfelelő nevű üzenetet dolgozza fel. (A megfelelő az answer utasításnak megfelelőt jelenti.) Könnyen látható, hogy az az állítás, hogy az üzenetek végül biztosan megválaszolódnak ekvivalens azzal, hogy az üzenetek egy sorban tárolódnak, ha feltesszük, hogy a végrehajtási sebesség pozitív. Itt látszik az Adától való különbség. Az Adában minden entrynek külön üzenetpuffere van, és a különböző pufferek közötti fairség nem biztosított, azaz lehetséges, hogy végtelen sok azonos nevű üzenetet dolgozunk fel az select utasításban, miközben végig nyitott a másik, üzenetet tartalmazó answer ág.