V 1.0 OE-NIK HP 1 Haladó Programozás Szálkezelés + szinkronizáció
V 1.0 Többszálú programok főbb lehetséges problémái Versenyhelyzet („race condition”) –A párhuzamosan futó szálak kommunikációjához szinte biztosan szükség van közös erőforrások (memória (közös változók), portok, I/O eszközök, fájlok) használatára –Ha ezek állapotát egy szál módosítja, de közben más szálak is hozzájuk férnek, akkor az utóbbi szálak könnyen hibás vagy félkész adatokhoz juthatnak Holtpont („deadlock”) –A szál olyan erőforrásra várakozik, ami sohasem érkezik meg –Két vagy több szál egymásra várakozik (így egyik sem fejeződik be soha) OE-NIK HP 2
V 1.0 Versenyhelyzet demó Készítsünk olyan programot, amiben több szál egy közös eredményt számol. (Pl: „együtt” elszámolnak ig + Thread.Sleep-ek.) Készítsünk olyan konzolos programot, amiben több szál egy-egy saját eredményt számol, és az eredmény %-os állapotát saját színnel %-onként kiírják a konzolba OE-NIK HP 3
V 1.0 Versenyhelyzet példák Szál 1Szál2 Eredeti kódt = x x = t + 1 u = x x = u lehetséges kimenetel(x értéke 0) t = x x = t + 1 (x értéke 1) u = x x = u + 2 (x értéke 2) 2. lehetséges kimenetel t = x x = t + 1 (x értéke 0) u = x x = u + 2 (x értéke 2) 3. lehetséges kimenetel(x értéke 0) t = x x = t + 1 (x értéke 1) u = x x = u + 2 (x értéke 3) OE-NIK HP 4
V 1.0 Versenyhelyzet Sok nyelvi konstrukció használatából is adódhat versenyhelyzethez vezető állapot! X++ –A ++ az utasítások szintjén különálló olvasás és írás műveletből áll. (Memóriából kiolvasásig jut el „A” szál, amikor jön „B” szál és pl. elvégzi az X növelését. „A” szál számára azonban az X még a memóriából kiolvasott értéket viseli) X += 2 –A += az utasítások szintjén különálló olvasás és írás műveletből áll. Mint előbb. A[i] += 1 –Előfordulhat, hogy másik szál épp ugyanezen indexre dolgozna (esetleg más néven). Mint előbb. Természetesen magas szinten is létezik a probléma if (!list.Contains(key)) list.Add(key); A példák forrása: OE-NIK HP 5 Másik szál a két hívás lefutása között beillesztheti ugyanezt a kulcsot
V 1.0 Holtpont demó Készítsünk olyan WPF-es programot, amely hosszú műveleten dolgozik, és az állapotát (eredmény kiszámolásának %-os állását) kiírja egy progressbar-ra Legyen egy gomb, amit megnyomva Wait-eljünk rá az eredményt számoló taszkra, és ha az véget ért, írjunk ki valamit (pl: „az eredmény elkészült”) Nem adatkötéssel csináljuk –Adatkötés esetén ugyanígy fellép a bemutatni kívánt probléma, csak a kiváltó okok nem láthatók OE-NIK HP 6
V 1.0 Holtpont Oka (példából): –Szál 1: hosszú műveletet végez, és néha Invoke()-ol, hogy kiírja az aktuális állapotát. –Főszál: a futás egy bizonyos pontján Join()/Wait()-val megpróbálja bevárni Szál 1-et. –Következmény: Szál 1 éppen az Invoke()-ban van, tehát főszálra vár, amikor a főszál pedig a Join()/Wait() miatt éppen Szál 1-re vár. Klasszikus holtpont példa (kritikus szakasszal; magyarázat később): –Erőforrásokat lockkal próbáltunk védeni. Mindkét szál már belépett az első kritikus szakaszba, ott viszont várakoznia kell, hogy hozzájusson a másik lehetséges erőforráshoz. OE-NIK HP 7 lock (a) { // feldolgozás lock (b) { // feldolgozás } 1. szál lock (b) { // feldolgozás lock (a) { // feldolgozás } 2. szál
V 1.0 Versenyhelyzet és holtpont kivédése Versenyhelyzet akkor áll fenn, amikor több szál próbál egyszerre azonos adathoz hozzáférni –Ha megoldható, ne használjunk a szálak számára közös objektumokat (referenciatípusok esetén különösen figyelni!) – Bizonyos feladatok/adatok esetén értéktípusok megfontolandók lehetnek –Ha ez nem megoldható, akkor szálszinkronizáció –De: OK a szálak számára közös adat, HA az elérés minden szál részéről read- only A holtpont kivédésére nincs megoldás (alapos tervezés!) OE-NIK HP 8
V 1.0 Szinkronizáció A szálszinkronizáció biztosítja, hogy párhuzamosan egymás mellett futó szálak nem azonos időben hajtanak végre egy adott programszakaszt Több formája van (kölcsönös kizárás, randevú) Sok programozói munkát igényel, extrém módon hibaérzékeny, rosszul tesztelhető –Nem minden hiba jön elő minden esetben, minden konfiguráció mellett… Lassít –Rossz tervezéssel gyakorlatilag sorossá redukálható a végrehajtás Ha lehet, próbáljuk elkerülni (külön adat a szálaknak stb.) –Sokszor nem lehet OE-NIK HP 9
V 1.0 Szinkronizáció kölcsönös kizárással Kritikus szakasz („critical section”) A programokon belül megjelölt kritikus kódrészletek soros végrehajtását biztosítja több párhuzamos szál esetén is. Amíg az egyik szál az „A” kritikus szakaszon dolgozik, a többi szál nem léphet be az „A” kritikus szakaszba..NET osztályok: System.Threading.Monitor (és a C# „lock” utasítása), System.Threading.Mutex, System.Threading.ReaderWriterLock stb. Szemafor („semaphore”) A kritikus szakasz általánosítása (többpéldányos erőforrások esetén egyszerre több szál belépését is lehetővé teszi)..NET osztály: System.Threading.Semaphore Atomi végrehajtás („interlocked execution”) Egyes egyszerű műveletek oszthatatlan végrehajtását biztosítja (igen gyors)..NET osztály: System.Threading.Interlocked Csővezeték („pipe”), konkurrens gyűjtemények Olvasható és írható tároló, amely szükség szerint várakoztatja az igénylőket. (Az ún. „termelő-fogyasztó” probléma megoldására készült.).NET-ben: System.Collections.Concurrent osztályai 10
V 1.0 Szinkronizáció bevárással (randevú) Esemény („event”) Lehetőséget ad alkalmankénti vagy rendszeres jelzésre (pl: szál jelzi, hogy végzett a feladatával)..NET osztályok: System.Threading.AutoResetEvent, System.Threading.ManualResetEvent stb. Időzítő („timer”) Relatív vagy abszolút időhöz való igazodást tesz lehetővé..NET osztályok: System.Windows.Forms.Timer, System.Timers.Timer, System.Threading.Timer stb. 11 OE-NIK HP
V 1.0 Példa szinkronizációra (a „lock” utasítás) using System; using System.Threading; class Program { private static int counter = 0; private static object lockObject = new Object(); static void Main(string[] args) { Thread t1 = new Thread(ThreadMethod); t1.Start(); Thread t2 = new Thread(ThreadMethod); t2.Start(); } private static void ThreadMethod() { lock (lockObject) { counter++; Thread.Sleep(500); Console.WriteLine("A számláló állása: " + counter); } A lock utasítás nélkül a metódus sorosan (egy szálon futtatva) helyesen működik, párhuzamosan (több szálon) azonban nem Figyelem: SOHA ne írjunk le az alábbiakra hasonlító kódot: lock (this) vagy lock (typeof(Program)) azaz sose lockoljunk olyat, amiről nem tudjuk befolyásolni, ki fér hozzá OE-NIK HP
V 1.0 Feladat A és B barátságos számok, ha osztóösszegük kölcsönösen egyenlő a másik számmal Vagyis: „A” osztóösszege (önmagát kihagyva) B „B” osztóösszege (önmagát kihagyva) A Feladat: írjuk ki az összes barátságos számot ig a lehető leggyorsabban Osztóösszegek meghatározása: –Szokványos módszerrel: Ciklus i=2-től Gyök(X)-ig, minden osztót és osztópárt hozzáadok az összeghez sok osztás –Eratosthenes szita: a 2-t mint osztót hozzá kell adni az osztóösszeghez a következő számoknál: 4, 6, 8, … A 3-t mint osztót a 6, 9, 12 számoknál … etc OE-NIK HP 13
V 1.0 Feladat Készítsünk többszálú konzolos alkalmazást, amely során két szál használja ugyanazt az erőforrást, amit esetünkben egy számokat tároló sor jelképez. Az egyik szál feladata a sor feltöltése, a másik feladata a sorból egy elem feldolgozása (kivétele). A fejlesztés közben gondoskodjunk a szálak szinkronizációjáról is! Ötletek: –Először egy egyszerű megvalósítással döntsük el, szükség van-e szinkronizációra, majd ha úgy ítéljük meg, hogy igen, akkor használjuk a lock utasítást vagy a Monitor osztály statikus Enter(), illetve és Exit() metódusát –Szinkronizáció esetén a jobb teljesítmény érdekében igyekezzünk a lehető legrövidebbre venni a kritikus szakaszt 14 OE-NIK HP
V 1.0 Megoldás OE-NIK HP 15
V 1.0 Irodalomjegyzék J. Richter: CLR via C#, Fourth Edition Kiadó: Microsoft Press, 2012 Web: Nyelv: angol További források: MSDN, Miklós Árpád prezentációi 16 OE-NIK HP