A párhuzamos programozás nyelvi elemei Legéndi Richárd Olivér Eötvös Loránd Tudományegyetem november 12.
Kivonat Bevezetés Alapfogalmak Példák Alapfeladatok, eszközök Párhuzamos nyelvek általános jellemzése Nyelvi lehetőségek: Ada, Java, PVM
Párhuzamosság „Concurrency is hard and boring. Unfortunately, my favoured technique of ignoring it and hoping it will go away doesn't look like it's going to bear fruit.”
Párhuzamosság Több részfeladat egyidejűleg történő végrehajtása Miért? A feladat logikai szerkezete A program több, fizikailag is független eszközön fut Hatékonyság
Hatékonyság – Amdahl’s law Vannak folyamatok, amik gyorsíthatók több erőforrással – és vannak, amik nem Egy felső határt ad a gyorsulás mértékére, ha ismert a párhuzamosan végrehajtandó utasítások aránya: P : párhuzamosítható műveletek aránya N : processzorok száma
Hatékonyság – Amdahl’s law Ha N ∞, akkor speedup 1 / P Ha egy program 50%-a szekvenciális, akkor maximum 2x-es gyorsulás várható, a processzorok számától függetlenül P : párhuzamosítható műveletek aránya N : processzorok száma
A párhuzamosság szintjei Utasítások Taskok Folyamatok (processes) Szálak (threads) Viselkedésük alapján lehetnek: Függetlenek Versengők Együttműködők
Modula-2 Korutinok (startprocess()-szel indíthatók) Jeleket küldhetnek (send), fogadhatnak (wait) awaited(): megadja, hogy van-e legalább egy olyan folyamat, amely jelre vár TRANSFER(, ) Megszakítja p1 processzt, és átadja a vezérlést p2-nek Amikor egy folyamat aktív lesz, onnan folytatja, ahol az előző vezérlésnél megállt
SIMULA 67 Korutinok Alárendeltségi viszony a létrehozójával (szülő-gyermek) detach(): a gyermek visszaadja a vezérlést a szülőnek call(): a leválasztott folyamat újraaktiválása resume(X): az X korutin javára lemondás a vezérlésről
Portal A folyamatoknak különböző állapotai is lehetnek: Aktiválható – megkaphatja a vezérlést Felfüggesztett Várakozó – egy jelre vár
Algol 68 Begin – end közé írt, vesszővel elválasztott utasítások párhuzamosan futtathatók A blokk nem fejeződik be, amíg minden párhuzamos klóz véget nem ér Szinkronizálás: Dijkstra szemaforokkal
Concurrent Eiffel Párhuzamosság a separate kulcsszóval Lehet osztály, metódus, függvény is Eljárás esetén nem függesztődik fel Függvény esetén megvárja a hívó az eredményt Külön virtuális processzoron futnak
Modula-3 A folyamatokat szálaknak nevezi A párhuzamosság eszközei: Thread.Fork, Thread.Join Közös memóriaterületen kommunikálhatnak, üzenetekkel – Channel
Ada Task-objektumok ábrázolják a folyamatokat Entry pointokkal – más taskokból hívhatók A task törzsében minden entry-hez legalább egy accept tartozik A hívó folyamat a randevú fogadásáig, és az accept végrehajtásáig felfüggesztődik (randevú)
Tényleges párhuzamosság? Látszat – pl. single core gépben Egyszerre egy folyamatot hajt végre, de adott időtartam alatt akár többet is Valódi párhuzamosság – pl. többmagos, többprocesszoros gépekben
Alapfeladatok 2 alapvető probléma Kommunikáció Kommunikációs közeg: socket, signal handler, fájl, osztott memória, etc. Szinkronizáció: Folyamatok összehangolása aszinkron – szinkron
Kölcsönös kizárás, szinkronizáció Szinkronizáció: olyan folyamat, amellyel meghatározható a folyamatokban szereplő utasítások relatív sorrendje Kölcsönös kizárás: osztott változók biztonságos használatához Kritikus szakasz: program azon része, ahol egy időben csak egyetlen folyamat tartózkodhat Atomi művelet: bármilyen közbeeső állapota nem látható a többi folyamat számára
Miért van rájuk szükség? Közös erőforrások használata X = X + 1 (nem feltétlen 2 művelet) Pl. 32 bites JVM + long, 2 regiszterben van tárolva 2 olvasás + 2 írás
Felmerülő problémák Azon túl, hogy megbízhatóság… Holtpont – kölcsönösen egymásra várakoznak a folyamatok, és egyik sem tud tovább haladni Kiéheztetés – több folyamat azonos erőforrást használ, és valamelyik ritkán fér csak hozzá Versenyhelyzetek – amikor egy számítás helyessége függ a végrehajtó folyamatok sorrendjétől (pl. check-then-act blokkok) A szinkronizációt ezen problémák elkerülésével kell megoldani
Kölcsönös kizárás Belépési – kilépési protokollok Több módszer: Tevékeny várakozás Szemafor Monitor Feltételes kritikus szakasz Nyelvfüggő elemek (pl. Ada védett objektumai) Azonos invariánsban szereplő változókat ugyanazzal a lockkal védeni.
Tevékeny várakozás Amíg a kívánt helyzet elő nem áll, addig a folyamat dinamikusan várakozik Kritikus szakaszok kezelésére szolgál Hátránya: A folyamat várakozás közben is foglalja a processzoridőt
Szemafor Dijkstra, ’68 Szintén kritikus szakaszok védelmére Általános forma: s – szemafor, s >= 0 egész értékkel P(s) – belépési protokoll (passeer) (s > 0) s-- Kritikus szakasz V(s) – kilépési protokoll (verhogen) s++ P, S atomi műveletek
Szemafor Bináris szemafor (mutex, lock): s := 1 Ha s > 1: hány folyamat léphet be egyszerre a kritikus szakaszba Előnye: Segítségével hatékony program készíthető Hátránya: A strukturáltság ellen hat
Miért nem elég a szemafor? Cigarette smokers problem (1971, S. S. Patil) 3 erőforrás: Dohány – Papír – Gyufa 3 láncdohányos, mindegyiknek 1 erőforrás végtelen 1 felügyelő Elvesz 2 véletlen szereplőtől 1-1 nyersanyagot, az asztalra rakja, majd értesíti a harmadikat, hogy készíthet egy cigarettát Mind a 3 alapanyagra szükség van a dohányzáshoz Probléma: megoldani ezt a szituációt, kizárólag egyfajta szinkronizációs primitívvel
Cigarette smokers problem II Kikötések: Az ágens kód nem változtatható meg Nem használható feltételes utasítás, sem több szemafor Az első érthető, a második kb. minden nem triviális problémát megoldhatatlanná tesz…
Cigarette smokers problem III Megoldás (ha a 2. feltételt elhagyjuk): Felügyelő: i. dohányos: while true { P( T ); i, j választása (nemdet.) k legyen a 3. dohányos V( A[k] ); } while true { P( A[i] ); cigarettát készít V( T ); dohányzik } A[] = [0,0,0] : szemafor tömb T = 1 : asztal szemafor (bináris szemaforok)
Monitor A változókat és a rajtuk értelmezett műveleteket úgy zárja egységbe, hogy a monitorhoz tartozó műveletek közül egyidőben csak egy lehet aktív Belső adatszerkezetei kívülről csak a monitor műveletein keresztül érhetők el Feltételes változók, várakozási sorokkal: c : feltétel wait(c) : a folyamat blokkolódik, és bekerül c várakozási sorába; elindítani más fogja signal(c) : ha van várakozó folyamat, az elsőt elindítja (ha nincs, nem történik semmi) Előnye: A szemafornál strukturáltabb || nyelvi elem
Monitor a Concurrent Pascalban Itt a monitor osztott változók egységbe zárása (osztály pedig absztrakt adattípus) Egy osztály folyamathoz vagy monitorhoz köthető A monitor biztosítja az adatok védelmét, a monitor eljárásai pedig egy várakozási sorban állnak – egyszerre csak egy hajtódik végre
Feltételes kritikus szakasz Kritikus szakaszban a folyamat a védett változóhoz csak ellenőrzötten férhet hozzá A megadott őrfeltételnek teljesülnie kell, mielőtt a vezérlés belép
Párhuzamos nyelvek általános jellemzése A párhuzamos programok végrehajtása nem determinisztikus fő szempont a hatékonyság, biztonság A vezérlésre különböző modellek léteznek: Ellenőrzésvezérelt (control driven computation) Az elindított folyamatok ellenőrzése fork, join, wait típusú parancsokkal történik Adatvezérlésű (data driven computation) A folyamatok végrehajtási sorrendje az adatszerkezetektől, és a közöttük lévő összefüggésektől függ. A folyamat végrehajtódik, ha minden adat a rendelkezésre áll Igényvezérlésű (demand driven computation) A folyamatok akkor hajtódnak végre, amikor ezeket egy másik folyamat kéri
Párhuzamos nyelvek osztályozása Osztott változókkal rendelkező nyelvek Üzenetküldéssel rendelkező nyelvek Osztott memóriát használó, adatvezérlésű nyelvek OO párhuzamos nyelvek Funkcionális nyelvek Logikai nyelvek Egy nyelv több kategóriába is eshet
Funkcionális nyelvek 2 legfontosabb művelet: Kifejezések egyszerűsítése és kiértékelése Egy függvényhívás egy folyamatnak tekinthető A kiértékelések bizonyos annotációk, kiértékelési szabályok alapján párhuzamosan is történhetnek Pl. Concurrent Clean, Distributed Haskell
Logikai nyelvek Szabályok kiértékelése párhuzamosan is történhet Pl. Concurrent Prolog, Parlog Parlog: And/Or párhuzamosság Balról jobbra, az And ‘.’ műveleti jelekkel elválasztott kifejezéseket párhuzamosan értékeli Ha nem sikerül, az Or ‘;’ utáni műveletekkel próbálkozik Kommunikáció: közös változókon keresztül Szinkronizáció: automatikus
Szekvenciális OO nyelvek Az alábbi 3 feltétellel: A program futása pontosan 1 objektum létrehozásával indul Ha egy objektum üzenetet küld egy másiknak, akkor megvárja az üzenet feldolgozását Egy objektum csak akkor aktív, ha éppen egy bejövő üzenet feldolgozására egy metódust futtat
Párhuzamos OO nyelvek Párhuzamosság úgy érhető el, ha valamely feltételt elhagyjuk: Egyszerre több folyamat indul Az üzenetküldő nem várja meg az üzenet fogadását (aszinkron kommunikáció) Az objektumok nem várnak passzívan az érkező üzenetekre
Szálbiztos osztályok tervezése Ha több folyamat ugyanazt az állapotváltozót használja, szinkronizáció nélkül, az baj. 3 módon javítható: Ne használják közösen ugyanazt az állapotváltozót, vagy Legyen az állapotváltozó megváltoztathatatlan (immutable), vagy Szinkronizáció használata a változó használatánál Biztonságos osztályok tervezésénél az OO technikák fontosak (enkapszuláció, immutability, az invariánsok tiszta specifikációja)
Szálbiztos osztályok tervezése Egy osztály szálbiztos, ha több szálból, az ütemezésre való tekintet nélkül, valamint a kliens kódban elhelyezett egyéb szinkronizáció nélkül is helyesen viselkedik A biztonságos osztályok enkapszulálják a szükséges szinkronizációt, így a használójának nem kell róla gondoskodnia Az állapot nélküli osztályok mindig biztonságosak
Szálbiztos osztályok tervezése Egy objektum immutable, ha: Az állapota nem változtatható meg a létrehozása után Minden adattagja immutable Helyesen lett létrehozva Az immutable osztályok mindig biztonságosak
Ada95 Taskok, egymástól független virtuális processzorokon Kommunikáció: a szinkronizációs pontokon, randevú keretein belül, a hívó és hívott között Nyelvi elemek: entry, accept – taskok közötti kommunikáció leírásához delay t – időzítés, felfüggeszt legalább t mp-re select – kommunikációs típusok megadása abort – végrehajtás megszüntetése
Taskok definiálása task Hello is -- specifikacio entry msg( s : String := ”Hello World”); end Hello; task body Hello is -- torzs begin accept msg(s : String := ”Hello World”) do put_line(s); end msg; end Hello;
Task objektumok Minden task objektum, így van típusa is Az ugyanolyan típusú taskok ugyanazt az utasítássorozatot hajtják végre, önállóan Limited típus (értékadás, =, /= nem használható rájuk) művelethalmaza az attribútumaira korlátozódik az objektumok értéke állandó a futás során Lehet diszkrimináns része is A tasktörzsben definiált objektumokra a kölcsönös kizárás automatikusan biztosított
Taskok indítása, végrehajtása Indításuk az objektumot bevezető deklarációs rész feldolgozása után, az 1. utasítás végrehajtása előtt történik Ha hiba történik, komplett állapotba kerül, és a taskot tartalmazó programegységben Tasking_Error lép fel Egy befejezett taskot nem lehet újra elindítani
Belépési pontok Kommunikáció: a belépési pontokon keresztül, közvetlenül a paramétereken keresztül Minden entry-hez legalább egy accept Ha egy task meghívja egy másik belépési pontját: és az válaszol rá, akkor randevú jön létre; különben vár rá ha több task is hívta, akkor várakozási sor (FIFO) Egy task saját belépési pontjának hívása holtpontot eredményez!
Select Lehetővé teszi, hogy egy adott task többféle hívást várjon és fogadjon task body SelectTask is begin loop select accept F1(…) do … end F1; or accept F2(…) do … end F2; end select; end loop; end SelectTask
Feltételes hívásfogadás A select alternatívákat feltételekhez köthetjük when => Egy ág nyitott, ha nincs feltétele, vagy teljesül Különben zárt A feltételek a select elején, 1x értékelődnek ki Ha minden ág zárt Programming_Error Lehet else ág: ha egyik belépésipont-hívást sem lehet kiszolgálni
Randevúk Különféle randevúk is megoldhatók a select, accept, delay használatával: szelektív hívás szelektív fogadás feltételes hívás időhöz kötött hívás etc.
Java Szálakkal dolgozik 2 lehetőség van a létrehozásukra: 1. Thread: osztály, származtatással start()/stop()/run()/etc. new Thread() { public void run() { System.out.println(”Thread”); } }.start();
Java 2. Runnable: interfész, implementálással (nincs többszörös öröklődés, pl. Appletekhez) Egyetlen előírt metódus: run() new Thread(new Runnable() { public void run() { System.out.println(”Thread”); } }).start();
Szálak attribútumai, műveletei Megszakítás: interrupt() Lehet démon szál is: setDaemon(true) Csak akkor áll meg, ha a többi, nem démon szál is megállt már Szinkronizáció: join() Prioritással is rendelkezhetnek (1-10) Round-Robin módszerrel ütemeződnek ***
A yield()/wait(0) szemantikája „… a Thread osztály yield() eljárásának hatására a hívó szál a futási sor végére kerül.” (539. o.) DE! Ezek szemantikája nem definiált a JLS szerint. A JVM implementációk nop utasításként, ütemezési javaslatnak is kezelhetik. memory.html# memory.html#17.9 Java yield(), wait(0) =/= Unix rendszerek hasonló utasításával Habár a Sun JVM így implementálta…
ThreadGroup A szálak csoportokba szervezhetők (egyszerre egyben lehetnek, ez nem változtatható meg) Csoportosan kezelhetők (leállítás, megszakítás, etc.) Csoportok újabb csoportokat tartalmazhatnak hierarchikus szerkezet
Szinkronizálás Monitorokkal, szinkronizációs burkokkal A monitorok újrahívhatók synchronized =/= static synchronized Ezen kívül: wait(), notify(), notifyAll() Időhöz is köthetők
Volatile Akkor használjuk, ha egyszerűsíti a kódot. Ha már gondolkozni kell, hogy miért is lehet ott volatile… Használható, ha: Egy változó új értéke nem függ a régitől, vagy egyetlen szál módosíthatja csak; A változó nem szerepel invariánsban egyéb más állapotváltozókkal; és Egyéb okból nem szükséges lockolás a változó módosításához. Tipikusan shutdown hookoknál: volatile boolean run = true; while (run) { … }
JDK 6.0 java.util.concurrent java.util.concurrent.atomic java.util.concurrent.lock Változatos, hatékony eszközök: Barrier, Semaphor, FutureTask, … Adatszerkezetek: ConcurrentHashMap, BlockingQueue, … Lockok, pl. ReentrantLock Atomi változók: AtomicLong, AtomicReference, …
Paralell Virtual Machine (PVM) Hálózatba kapcsolt számítógépeket egyetlen nagy, || virtuális számítógépként kezelése 2 részből áll: Démon: a rendszerbe kapcsolt számítógépeken fut 1-1 példány, a virtuális gép működtetése a feladata Programkönyvtár: folyamatindítás és kezelés A folyamatok üzenetküldéssel kommunikálhatnak Minden folyamatnak egyedi azonosító (tid)
PVM C API #include pvm_mytid: megadja a folyamat azonosítóját pvm_exit: a folyamat kilép a rendszerből (működhet tovább, de PVM hívásokat nem hajthat végre) pvm_spawn: új folyamat indítása pvm_kill: folyamat leállítása pvm_pstat: információ minden futó folyamatról
Kommunikáció Üzenetküldéseken keresztül 1 folyamatnak 1 aktív küldő vagy fogadó tárja lehet pvm_mkbuf: új tár létrehozása pvm_initsend: törli az aktuális tárat, és újat hoz létre Üzenetküldés: be-, ill. kicsomagolással Becsomagolás: pvm_pk[byte, short, str, …] Kicsomagolás: pvm_upk[byte, short, str, …]
Kommunikáció II Üzenet küldése: pvm_send: egy folyamatnak pvm_mcast: több folyamatnak (egyenértékű a pvm_send többszöri hívásával) Üzenet fogadása: pvm_recv: meghatározható, hogy kitől, milyen típusú adatot (akár akárkitől akármit). Az üzenet megérkeztéig blokkolódik a folyamat pvm_probe: lekérdezhető, hogy van-e a paraméterezésnek megfelelő üzenet
Folyamatok pvm_joingroup, pvm_lvgroup: A folyamatok csoportba szervezhetők pvm_broadcast: csoporüzenet küldésére pvm_barrier: Több folyamat szinkronizációja
Források Programozási nyelvek, Nyékyné Gaizler Judit et al., Kiskapu, Java Concurrency in Practice, Brian Goetz et al., Addison-Wesley Professional, Az ADA95 programozási nyelv, Nyékyné Gaizler Judit et al., ELTE Eötvös Kiadó, Ada Wikibook The Java Language Specification, 3rd ed.,
Párhuzamosság más szinten SISD / MISD / SIMD / MIMD Kép-, és jelfeldolgozás (AMD lib):
Kérdések ?
Ami kimaradt… Hyper-threading, superscalar architektúrák.NET környezet Google Go?
Köszönöm a figyelmet! Legéndi Richárd Olivér Eötvös Loránd Tudományegyetem, Informatikai Kar Programozási Nyelvek és Fordítóprogramok Tanszék