Párhuzamos programozás Készítő: Nagy Tibor István
Párhuzamos feldolgozás Több feladat végrehajtása egy időben Párhuzamos feldolgozás: Valós párhuzamosság (több processzor) Látszólagos párhuzamosság, multitasking (egy CPU)
Multitasking Futtatható feladat: folyamat (process) A folyamat az a „környezet”, amelyben a program fut Egy folyamaton belül egy program futhat Folyamat: Kernel szintű (op. rendszer) Saját címtere van (a memóriában) Saját kontextussal rendelkezik Prioritással rendelkezik Más folyamatok nem férhetnek hozzá az ő adataihoz
Multitasking Párhuzamos feldolgozás: Több folyamat futhat egymással párhuzamosan A párhuzamosságot a folyamatok közti váltogatással valósítja meg az operációs rendszer A folyamatok közti váltás a folyamatok prioritása alapján történik
Szálak A párhuzamosság egy újabb szintje A szálak: Folyamaton belül találhatók Az egy folyamaton belüli szálak egymással párhuzamosan futnak Az egy folyamaton belüli szálak közösen használják a folyamat erőforrásait Kezelésük legtöbbször program, ill. operációs rendszer szintjén történik
Szálak létrehozása A Thread osztályból örökléssel A Runnable interfész implementálásával Mindkét esetben egy „run” nevű, paraméterek nélküli metódust kell készíteni, amely a szál által elvégzendő utasításokat tartalmazza
Szálosztály létrehozása Thread osztállyal public class SzamlaloSzal extends Thread{ private int n=0; public void run(){ while(n<100) System.out.println(n++); }
Szál elindítása public class Foprogram{ public static void main(String args[]){ SzamlaloSzal sz1=new SzamlaloSzal(); SzamlaloSzal sz2=new SzamlaloSzal(); sz1.start(); sz2.start(); }
Szálosztály létrehozása Runnable interfésszel public class SzamlaloSzal implements Runnable{ private int n=0; public void run(){ while(n<100) System.out.println(n++); }
Szál elindítása public class Foprogram{ public static void main(String args[]){ SzamlaloSzal ss1=new SzamlaloSzal(); SzamlaloSzal ss2=new SzamlaloSzal(); Thread sz1=new Thread(ss1); Thread sz2=new Thread(ss2); sz1.start(); sz2.start(); }
Szál leállítása A szálobjektum stop() metódusának meghívásával – NEM BIZTONSÁGOS! (deadlock) Biztonságos módszer: A szál objektum egy adattagjával A szál az adattag értékétől függően fut, vagy áll le
Szál leállítása public class SzamlaloSzal extends Thread{ private int n=0; public boolean allj=false; public void run(){ while(!allj){ System.out.println(n++); ... }
Szál életciklusa Létrehozva Megszakítva Kész Fut Blokkolt Halott interrupt() start() interrupt() eseményre vár wait(), sleep(), I/O ütemező Kész Fut Blokkolt yield(), ütemező esemény megtörtént notify(), idő letelik, I/O vége run() lefutott Halott
Szál életciklusa Fut: Kész: Blokkolt: Halott: Ha meghívtuk a start() metódust Ha visszatér „Blokkolt” állapotból Ha visszatér „ Kész” állapotból Kész: Ha egy másik szál kapta meg a futás jogát Blokkolt: Ha I/O művelet befejezésére vár Ha bizonyos idő letelésére vár ( sleep() ) Ha valamilyen feltétel teljesülésére vár ( wait() ) Halott: Ha a run() metódus futása befejeződött
Szálak ütemezése Java: Operációs rendszer: Rögzített prioritásos ütemezés Preemptív ütemezés Operációs rendszer: Rendszerenként eltérő ütemezés Egy Java programbeli szál futtatása esetén a két ütemezés együttesen működik
Rögzített prioritásos ütemezés Egy szál prioritása: Thread.MIN_PRIORITY-tól Thread.MAX_PRIORITY-ig Bármely időpillanatban a „Kész” állapotú szálak közül a legmagasabb prioritású szál fog futni (rögz. prior. üt.) Ha egy futó szálnál magasabb prioritású szál indul el, akkor az aktuális szál Kész állapotba kerül, az új szál pedig „Fut” állapotba (preemptív üt.) Azonos prioritású szálak „Round-Robin” módszerrel ütemeződnek
Önző és udvarias szálak Önző szál: működését csak run() metódusának lefutásakor függeszti fel Udvarias szál: saját működését bizonyos időközönként önként felfüggeszti a run() metódusának futása közben is: Thread.yield() wait() sleep()
Ütemezés problémái Kiéheztetés (starvation): Egy alacsonyabb prioritású szál soha nem kerül futó állapotba, mert mindig nála nagyobb prioritású szálak futnak. A modern op. Rendszerek ezt kiküszöbölik (UNIX, Windows). Holtpont (deadlock): Egy szál olyan feltétel teljesülésére vár, ami soha nem következik be
Démon szálak Háttérben futó feladatok (szolgáltatások) ellátására szolgál Csak akkor áll le, ha a programban az összes – nem démon – szál befejezte a működését Leállítását a futtató környezet végzi Egy szál démonná tétele: szálobjektum.setDaemon(true);
Termelő-fogyasztó probléma Két párhuzamosan futó szál bizonyos adatokat közösen használ Példa: Az egyik szál kiszámítja a négyzetszámokat A másik szál ezeket a számokat kiírja a képernyőre Az értékeket előállító szál a „termelő” (producer) Az értékeket felhasználó szál a „fogyasztó” (consumer)
Termelő-fogyasztó probléma – az előállító szál public class Producer extends Thread{ private NegyzetSzam nsz; public Producer(NegyzetSzam nsz){ this.nsz=nsz; } public void run(){ for(int i=0; true; i++) nsz.setSzam(i*i);
Termelő-fogyasztó probléma – a felhasználó szál public class Consumer extends Thread{ private NegyzetSzam nsz; public fogyasztó(NegyzetSzam nsz){ this.nsz=nsz; } public void run(){ for(int i=0; true; i++) System.out.println(nsz.getSzam());
Termelő-fogyasztó probléma – az előállított érték public class NegyzetSzam{ private int szam; public void setSzam(int szam){ this.szam=szam; } public int getSzam(){ return szam;
Termelő-fogyasztó probléma – a főprogram public class FoProgram{ public static void main(){ NegyzetSzam sz=new NegyzetSzam(); Producer p=new Producer(sz); fogyasztó c=new fogyasztó(sz); p.start(); c.start(); }
Szinkronizációs problémák A termelő gyorsabb, mint a fogyasztó: a fogyasztó néhány négyzetszámot kihagy Megoldás: a termelőnek meg kell várnia, amíg a fogyasztó beolvassa az előállított adatot A fogyasztó gyorsabb, mint a termelő: a fogyasztó néhány négyzetszámot többször is kiír a képernyőre Megoldás: a fogyasztónak meg kell várnia, amíg a termelő előállítja a következő adatot
Problémák kiküszöbölése - szinkronizálás Az adatcserét biztosító osztály lekérdező, ill. módosító metódusainak szinkronizálttá tétele (a két metódus nem fog párhuzamosan futni!!) Egy adattag bevezetése, amely jelzi, hogy az előállítandó adat készen áll-e Az adat lekérdezésénél megvárni, amíg az érték előáll Az adat előállításánál megvárni, amíg az előző érték felhasználásra került
Szinkronizálás – zárolás Az a szál, amelyik meghívja az adatcserét biztosító objektum egyik szinkronizált metódusát, kisajátítja magának ezt az objektumot, azaz zárolja azt Amíg az objektum zárolva van, másik szál nem férhet hozzá adattagjaihoz, metódusaihoz Java-ban a zárolás automatikusan történik
Szinkron működés – adat lekérdezése Kész=hamis? n i Várakozás Kész := hamis Adat kiolvasása
Szinkron működés – adat előállítása Kész=igaz? n i Várakozás Adat előállítása Kész := igaz
Szinkron működés – adatcserét biztosító osztály kódja public class NegyzetSzam{ private int szam; private boolean kesz; public synchronized void setSzam(int szam){ while(kesz){... wait(); ...} this.szam=szam; kesz=true; notifyAll(); } public synchronized int getSzam(){ while(!kesz){... wait(); ...} kesz=false; notifyAll(); return szam;
Adatcserét biztosító osztályból hívható metódusok wait() – felfüggeszti az aktuális szál működését, amíg egy másik szálból meg nem hívják ezen objektum notify(), vagy notifyAll() metódusát notifyAll() – „felébreszti” az összes szálat, amely ezen az objektumon blokkolódott notify() – „felébreszt” egy ütemező által kiválasztott szálat, amely ezen az objektumon blokkolódott
Adat előállítása, lekérdezése Főprogram p.start(), c.start() p szál c szál nsz.setSzam(i*i) sz objektum int szam boolean kesz getSzam() setSzam(int i) nsz.getSzam() szam=i*i wait() kesz=true wait() kesz=false notifyAll() nsz.setSzam(i*i) notifyAll() szam=i*i return szam kesz=true nsz.getSzam() notifyAll() ... ...
Példaprogramok http://javaalmanac.com/egs/java.lang/pkg.html