Folyamat- és szálkezelés Microsoft Windows 2000/XP/2003/Vista
Definíciók és folyamatmodell Folyamatok és szálak kezelése Ütemezési kérdések Többprocesszoros és többmagos rendszerek
© Miklós Árpád, BMF NIK, 2007
[email protected]
Folyamatmodell (1) Definíciók • Folyamat („process”) – Az operációs rendszerek programokat hajtanak végre • Kötegelt rendszerek: feladatok („job”) • Interaktív rendszerek: felhasználói programok („task”)
– A folyamat egy adott program dinamikus, a virtuális memóriában elhelyezkedő, „élő” példánya) – Fő adminisztrációs egység (állapotinformáció‐tár) • Erőforrások aktuális állapota (memória, fájlok, objektumkezelők, hálózati kapcsolatok...) • Processzoridő‐alapú elszámolást is lehetővé tesz
– Hagyományosan az ütemezés alapegysége • A Windows‐nál a szál („thread”) az ütemezés alapegysége
V1.0
2008. március 10.
© Miklós Árpád, BMF NIK, 2007
[email protected]
2
Folyamatmodell (2) Definíciók • Szál („thread”) – A Windows‐nál az ütemezés alapegysége • A processzorokon ténylegesen a szálak futnak, nem a folyamatok
– Folyamaton belüli állapotadminisztrációs egység • Processzorok, ütemezés, I/O műveletek állapotai
– Nincs saját címtere • Az egy folyamathoz tartozó szálak egymással osztoznak a folyamat címterén
– A többszálú működés problémákat vethet fel • Összehangolási pontok (randevú, kilépés) • Adatkezelés – –
V1.0
2008. március 10.
Szálspecifikus adatok kezelése Szál szempontjából globális adatok kezelése
© Miklós Árpád, BMF NIK, 2007
[email protected]
3
Folyamatmodell (3) A Windows‐nál alkalmazott négyszintű modell • 1. szint – feladat („job”) – Folyamatkészlet – Szabályozhatók a benne lévő folyamatok paraméterei • CPU idő, munkakészlet, biztonsági beállítások stb.
– Kötegelt feldolgozáshoz ideális
• 2. szint – folyamat („process”) – A szokásos értelemben vett (teljes kontextusú) folyamat • Egy adott program dinamikus, a virtuális memóriában elhelyezkedő, „élő” példánya, lásd korábban
V1.0
2008. március 10.
© Miklós Árpád, BMF NIK, 2007
[email protected]
4
Folyamatmodell (4) A Windows‐nál alkalmazott négyszintű modell Szálak állapotátmeneti diagramja
• 3. szint – szál („thread”) – Ütemezési alapegység – Lehetséges állapotok: • • • • • • •
Standby (3)
Initialized (0)
Preempció
0: Indítható („initialized”) 1: Futásra kész („ready”) 2: Futó („running”) 3: Készenléti („standby”) 4: Befejezett („terminated”) 5: Várakozó („waiting”) 6: Átmeneti („transition”)
Ready (1)
Running (2) Preempció, kvantum vége Önként lemondás
Transition (6) Kernelverem kilapozva
Waiting (5)
Terminated (4)
Original image © 2000‐2005 David A. Solomon and Mark Russinovich
• 4. szint – vékonyított szál („fiber”) – Kézi ütemezés a létrehozó szál kontextusán belül
V1.0
2008. március 10.
© Miklós Árpád, BMF NIK, 2007
[email protected]
5
Folyamatok és szálak kezelése (1) Folyamatok létrehozása és megszüntetése • Folyamatok létrehozása – 3 részben fut le 3 különböző kontextusban (létrehozó folyamat, Win32 alrendszer, létrejövő folyamat)
CMD.EXE
NTVDM.EXE
MS‐DOS .bat, .cmd
Win16
program.exe Win32 Win32
Milyen típusú a program? OS/2 1.x OS2.EXE
POSIX
program.exe (speciális WoW64 támogatással)
(64 bites Windows)
MS‐DOS .exe, .com, .pif
POSIX.EXE
NTVDM.EXE
Original image © 2000‐2005 David A. Solomon and Mark Russinovich
V1.0
2008. március 10.
© Miklós Árpád, BMF NIK, 2007
[email protected]
6
Folyamatok és szálak kezelése (2) Váltások a processzorok birtoklásában • Kontextusváltás – Mindig szálak között történik
T1 szál Futó
Megszakítás vagy rendszerhívás
Állapotmentés (TCB1) Állapotbetöltés (TCB2)
Futásra kész vagy várakozó
Megszakítás vagy rendszerhívás
T2 szál Futásra kész vagy várakozó
Futó
Állapotmentés (TCB2) Állapotbetöltés (TCB1) Futásra kész vagy várakozó
Futó Original image © 2000‐2005 David A. Solomon and Mark Russinovich
V1.0
2008. március 10.
© Miklós Árpád, BMF NIK, 2007
[email protected]
7
Ütemezés (1) A Windows rövid távú ütemezési politikája • Eseményalapú ütemezés – Nincs központi ütemező modul a kernelben
• Az ütemezés alapvetően szálszinten történik – Minden folyamathoz tartozik legalább egy szál
• Preemptív, prioritásos, körbeforgó algoritmus – Időszelet‐alapú kötelező preempció – Mindig a legmagasabb prioritású futásképes szál fut – Külön várakozó sor minden prioritási szinthez
V1.0
2008. március 10.
© Miklós Árpád, BMF NIK, 2007
[email protected]
8
Ütemezés (2) A Windows általános ütemezési politikája • Preempció (futásmegszakítás) esetei: – Lejár a szál időszelete • Újraütemezéskor az addig futó szál időszelete 3‐mal csökken
– Elindul egy nagyobb prioritású szál • Az előző az időszelet megmaradt részét később visszakapja
– A szál eseményre kezd várni – A szál önként feladja a futás jogát • Szigorú értelemben véve nem minősül preempciónak
– Külső megszakítás következik be – Megszűnik a szál
V1.0
2008. március 10.
© Miklós Árpád, BMF NIK, 2007
[email protected]
9
Ütemezés (3) Prioritások • Prioritások kezelése – Kétszintű rendszer (folyamatok és szálak; 0–31‐ig)
0
15 16 High
31 Realtime
13
24
Above Normal 10 Normal 8 Below Normal 6 Idle 4 V1.0
2008. március 10.
© Miklós Árpád, BMF NIK, 2007
[email protected]
10
Ütemezés (3) Prioritások • Prioritások kezelése – Kétszintű rendszer (folyamatok és szálak; 0–31‐ig) Folyamatok prioritási osztályai Realtime
Szálak prioritási szintjei
High
Above Normal
Normal
Below Normal
Idle
Time Critical
31
15
15
15
15
15
Highest
26
15
12
10
8
6
Above Normal
25
14
11
9
7
5
Normal
24
13
10
8
6
4
Below Normal
23
12
9
7
5
3
Lowest
22
11
8
6
4
2
Idle
16
1
1
1
1
1
– Lapnullázó szál: 0 – Üresjárati szál: „‐1” – Windows Vista: multimédia ütemezőszolgáltatás (MMCSS) V1.0
2008. március 10.
© Miklós Árpád, BMF NIK, 2007
[email protected]
11
Ütemezés (4) Időszeletek • A körbeforgó ütemezés következményei – A processzorintenzív alkalmazásoknak kedvez – Az időszelet erősen befolyásolja az észlelt teljesítményt
• Az időszelet („quantum”) hossza – A Normál prioritási osztálynál módosítható • Hossz, típus, előtérben lévő alkalmazás kiemelése • Beállítások: – –
1 időszelet = 3*x rendszerórajel‐ütem Újraütemezésnél 3‐mal csökken az időszelet hossza
• Windows Vista: a megszakításban töltött idő (igazságos módon) már nem csökkenti az időszelet hosszát V1.0
2008. március 10.
© Miklós Árpád, BMF NIK, 2007
[email protected]
12
Ütemezés (5) Beépített kompenzációs mechanizmusok • Prioritás növelése („priority boosting”) – I/O művelet befejezésekor – – – –
Képernyőmeghajtó & lemezmeghajtók: 1 Soros port & hálózat: 2 Billentyűzet & egér: 6 Hangeszközök: 8
– Várakozó állapotból kilépéskor • Eseményre, szemaforra stb. várakozás: 1 • Előtérben lévő folyamat befejezte a várakozást: 2
– A szál által várt GUI (ablak) esemény beérkezésekor: 2 – Régóta készenlétben álló szálaknál • 2 időszeletnyi időre 15‐re emeli a prioritást
• Időszelet módosítása („quantum stretching”) • A prioritásnövelés idejére megkétszereződik az időszelet V1.0
2008. március 10.
© Miklós Árpád, BMF NIK, 2007
[email protected]
13
Több processzor kezelése (1) Ütemezés egynél több, illetve többmagos CPU esetén • Négyféle kernelmegvalósítás: –NTOSKRNL.EXE – egy processzor •„ACPI Uniprocessor System” rendszereken régebbi CPU‐knál (Pentium I/II/III, AMD K5/K6/Athlon)
–NTKRNLPA.EXE – egy processzor, fizikai címkiterjesztés •„ACPI Uniprocessor System” rendszereken modern CPU‐knál (Pentium IV/M, Core/Core 2, AMD Athlon XP/64/Opteron)
–NTKRNLMP.EXE – több / többmagos processzor •„ACPI Multiprocessor System” rendszereken régebbi CPU‐knál
–NTKRPAMP.EXE – több / többmagos processzor, fizikai címkiterjesztés •„ACPI Multiprocessor System” rendszereken modern CPU‐knál
V1.0
2008. március 10.
© Miklós Árpád, BMF NIK, 2007
[email protected]
14
Több processzor kezelése (2) Ütemezés egynél több CPU, illetve többmagos CPU esetén • Eltérések az egyprocesszoros ütemezéshez képest – A legmagasabb prioritású szálak egyike kerül futó állapotba valamelyik processzor(mag)on • Terheléskiegyenlítés csak szálszinten
– Szimmetrikus elosztás (nincs „főprocesszor” és „alárendelt processzorok”) – Windows 2003 Server: minden processzorhoz (maghoz) külön „ready” állapotú várakozósorok
V1.0
2008. március 10.
© Miklós Árpád, BMF NIK, 2007
[email protected]
15
Több processzor kezelése (3) Ütemezés egynél több, illetve többmagos CPU esetén • Processzor kiválasztása futtatandó szálhoz – Processzoraffinitás: meghatározza, hogy mely processzorokon futhat egy‐egy folyamat (a hozzá tartozó szálak) • „Ideális” processzor – – –
A szál indulásakor véletlenszerűen kijelölt processzor
• „Következő” processzorok – – –
Azon processzorok, amelyeken a szál korábban már futott
• Futtatandó szál kiválasztása processzorhoz – Az ütemező igyekszik azonos processzoron tartani a szálakat
V1.0
2008. március 10.
© Miklós Árpád, BMF NIK, 2007
[email protected]
16
SzPE (C#) 2007–2008, II. félév BMF NIK
Párhuzamos programozás: .NET szálak használata Szálak kezelése Új szálak indítása, szálak felfüggesztése, állapotvezérlése és leállítása, szálak prioritási szintjei Előtér‐ és háttérszálak, ThreadPool szálak
Szinkronizáció A szinkronizáció alapfogalmai, versenyhelyzet és holtpont fogalma Kölcsönös kizárás biztosítása, a „lock” utasítás, szálak bevárása és randevúja
© Miklós Árpád, BMF NIK, 2007
[email protected]
Szálak • A szálak elsődleges célja a folyamatokon belüli párhuzamosítás A folyamatok adminisztrációja és váltása igen erőforrásigényes művelet, viszont az általuk nyújtott elszigetelés szintje egy programon belül csaknem mindig szükségtelen. Ezt az ellentmondást oldják fel a szálak, amelyek elszigetelést nem nyújtanak, gyors párhuzamos végrehajtást azonban igen. P3 folyamat
P1 folyamat T11 szál
T13 szál T12 szál
P2 folyamat
T31 szál
T22 szál T21 szál T23 szál
T24 szál
• A .NET keretrendszer támogatja a szálak kezelését is A keretrendszer kihasználja az operációs rendszer száltámogatását, de a .NET szálak és az operációs rendszer szálai között nem feltétlenül létezik 1:1 megfeleltetés (egy valódi szálon a keretrendszer több szála is futhat). V1.0
2008. március 10.
© Miklós Árpád, BMF NIK, 2007
[email protected]
18
A többszálúság megvalósítási lehetőségei • System.Threading.Thread osztály Lehetővé teszi szálak egyenkénti létrehozását, azonosítását, állapotvezérlését és megszüntetését. Kezelése egyszerű, viszont sok programozói munkát és pontosságot igényel.
• System.Threading.ThreadPool osztály Gyakran ismétlődő, rövid ideig tartó, erősen párhuzamos műveletekhez rendelkezésre álló „szálkészlet”, melynek használatával megtakarítható a szálak egyenkénti létrehozásának és megszüntetésének időigényes munkája. Kezelése egyszerű és hatékony, de a szálak egyéni identitását nem biztosítja.
• System.ComponentModel.BackgroundWorker osztály (később) A felhasználói felület kezelésének és a háttérben elvégzendő, esetenként igen sokáig tartó műveletek végrehajtásának szétválasztására szolgál. Kezelése igen kényelmes (az állapotváltozásokról események útján értesíti a felhasználó osztályt), ám korlátozott funkcionalitása miatt kevés célra alkalmas.
V1.0
2008. március 10.
© Miklós Árpád, BMF NIK, 2007
[email protected]
19
Threading névtér • A System.Threading névtér a következő „szálfeladatokat” oldja meg: – Futás idejű kontroll – szinkronizáció – thread pooling
•
System.Threading fontosabb típusai: – Alaposztályok: Thread és ThreadPool – Felsorolások: TreadState és ThreadPriority – Osztály: Monitor – Kivételek: ThreadAbortException és ThreadInterruptedException – delegate ThreadStart, WaitCallback, TimerCallback, IOCompletionCallback, … – …
V1.0
2008. március 10.
© Miklós Árpád, BMF NIK, 2007
[email protected]
20
Szálak kezelése (kivonatos referencia) • System.Threading.Thread osztály Metódusok Start() Suspend(), Resume() Abort() GetHashCode() Sleep() Join() Tulajdonságok CurrentCulture, CurrentUICulture IsBackground IsThreadPoolThread ManagedThreadID Name Priority ThreadState *
V1.0
Szál indítása Szál felfüggesztése, illetve folytatása Szál leállítása Szál azonosítójának lekérése Várakozás a megadott időintervallum elteltéig Várakozás az adott szál befejeződésére A szálhoz tartozó aktuális kultúra, illetve a szálhoz tartozó felhasználói felület kiválasztott nyelve Az adott szál háttérszál vagy előtérszál* Az adott szál a ThreadPool egyik szála‐e A szál egyedi azonosítója A szál megnevezése A szál prioritása (fontossági szintje) A szál aktuális állapota(i)
A programok futása véget ér, ha az utolsó előtérszál is lefutott (az esetleg még futó háttérszálak ekkor automatikusan megszűnnek). 2008. március 10.
© Miklós Árpád, BMF NIK, 2007
[email protected]
21
Thread osztály public sealed class Thread { public Thread(ThreadStart start);
• Konstruktor, paramétere: ThreadStart delegate
public ThreadPriority Priority {get; set;} public ThreadState ThreadState {get;}
• Aktuális állapot
public bool IsAlive {get;} public bool IsBackground {get; set;}
• Háttér
public void Start(); public static void Sleep(int time); public void Suspend(); public void Resume(); public void Join(); public void Interrupt(); public void Abort(); public static void ResetAbort(); public static Thread CurrentThread {get;}
• Szál vezérlő metódusok
• Az aktuálisan futó szál
} V1.0
2008. március 10.
© Miklós Árpád, BMF NIK, 2007
[email protected]
22
ThreadStart, ThreadPriority és ThreadState public delegate void ThreadStart(); public sealed class Thread { public Thread( ThreadStart start); public ThreadPriority Priority {get; set;} public ThreadState ThreadState {get;} …
public enum ThreadState { Unstarted, Running, Background, WaitSleepJoin, SuspendRequested, Suspended, AbortRequested, Stopped }
}
V1.0
public enum ThreadPriority { Highest, AboveNormal, Normal, BelowNormal, Lowest, }
2008. március 10.
© Miklós Árpád, BMF NIK, 2007
[email protected]
23
Thread – példa •
ThreadStart delegálthoz metódus
using System.Threading; public class ThreadExample { public static void RunT0() { for ( int i=0; i<10000; i++) { Console.Write( "x“ ); Thread.Sleep(100); } } //Thread készítése delegate to method RunT0 and starting it public static void Main(string[] args) { // Main thread starts a new thread which runs RunT0 method Thread t0 = new Thread( new ThreadStart(RunT0)); t0.Start(); } } V1.0
2008. március 10.
© Miklós Árpád, BMF NIK, 2007
[email protected]
24
Példa új szál indítására 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 V1.0
using System; using System.Threading; class Program { static void Main(string[] args) { Console.WriteLine("Szál közvetlen létrehozása"); Console.WriteLine("Főszál (sorszáma: {0})", Thread.CurrentThread.GetHashCode()); Thread newThread = new Thread(ThreadMethod); //.NET 1.1 esetén: Thread newThread = new Thread(new ThreadStart(ThreadMethod)); newThread.Name = "Új szál"; newThread.Start(); newThread.Join(); }
}
static void ThreadMethod() { Console.WriteLine("{0} (sorszáma: {1})", Thread.CurrentThread.Name, Thread.CurrentThread.GetHashCode()); }
2008. március 10.
ThreadExamples\Program.cs © Miklós Árpád, BMF NIK, 2007
[email protected]
25
Thread – példa Két szál, melyek folyamatosan a képernyőre írnak példánymetódussal using System; using System.Threading; class Printer { char ch; int sleepTime; public Printer(char c, int t) {ch = c; sleepTime = t;} public void Print() { for (int i = 0; i < 100; i++) { Console.Write(ch); Thread.Sleep(sleepTime); } } } class Test { static void Main() { Printer a = new Printer('.', 10); Printer b = new Printer('*', 100); new Thread(a.Print).Start(); new Thread(b.Print).Start(); } } V1.0
2008. március 10.
© Miklós Árpád, BMF NIK, 2007
[email protected]
26
Előtér és háttér szálak • Az előtér és háttér szálak közötti különbségek – Amíg előtér szál fut, addig a program nem terminál – A háttér szál futása nem gátolja meg a program terminálását • Az IsBackground tulajdonsággal állítható
Thread bgThread = new Thread(new ThreadStart(…)); bgThread.IsBackground = true;
V1.0
2008. március 10.
© Miklós Árpád, BMF NIK, 2007
[email protected]
27
Thread állapotok – példa Thread t = new Thread(P); Console.WriteLine("name={0}, priority={1}, state={2}", t.Name, t.Priority, t.ThreadState); t.Name = "Worker"; t.Priority = ThreadPriority.BelowNormal; t.Start(); Thread.Sleep(1); Console.WriteLine("name={0}, priority={1}, state={2}", t.Name, t.Priority, t.ThreadState); t.Suspend(); Thread.Sleep(1); Console.WriteLine("state={0}", t.ThreadState); t.Resume(); Console.WriteLine("state={0}", t.ThreadState); public enum ThreadState { t.Abort(); Unstarted, Thread.Sleep(1); Running, Console.WriteLine("state={0}", t.ThreadState);
Background, WaitSleepJoin, SuspendRequested, Suspended, AbortRequested, Stopped
Output name=, priority=Normal, state=Unstarted name=Worker, priority=BelowNormal, state=Running state=Suspended state=Running state=Stopped
V1.0
2008. március 10.
© Miklós Árpád, BMF NIK, 2007
[email protected]
}
28
Thread állapotdiagram Suspended safepoint reached
t.Resume Stopped
SuspendRequested end of thread method
t.Suspend Unstarted
t.Start
Thread.Sleep other.Join Wait(obj)
t.Abort
Running time over other joined Pulse(obj)
Exception caught, finally processed
AbortRequested
t.Interrupt o rt b t.A
WaitSleepJoin
V1.0
2008. március 10.
© Miklós Árpád, BMF NIK, 2007
[email protected]
29
Megvárás – példa (Join) using System; using System.Threading; class Test { static void P() { for (int i = 0; i < 20; i++) { Console.Write('-'); Thread.Sleep(100); } } static void Main() { Thread t = new Thread(P); Console.Write("start"); t.Start(); t.Join(); // waits for t Console.WriteLine("end"); } } Output: start--------------------end V1.0
2008. március 10.
© Miklós Árpád, BMF NIK, 2007
[email protected]
30
Abort kezelése – példa Az Abort speciális kivételt dob, amelyet kezelni lehet a metódusban using System; using System.Threading; class Test { static void P() { try { try { try { while (true) ; } catch (ThreadAbortException) { Console.WriteLine("-- inner aborted"); } } catch (ThreadAbortException) { Console.WriteLine("-- outer aborted"); } } finally { Console.WriteLine("-- finally"); } } static void Main(string[] arg) { Thread t = new Thread(P); t.Start(); Thread.Sleep(1); t.Abort(); t.Join(); Console.WriteLine("done"); } } Output -- inner aborted -- outer aborted -- finally done V1.0
2008. március 10.
© Miklós Árpád, BMF NIK, 2007
[email protected]
31
ThreadPools • ThreadPool szálak halmazát képes kezelni – Regisztrált feladathalmaz hatékony kezelése – Rövid idejű feladatok, inaktívak egy ideig
• Hátrány: – Az alkalmazói programnak nincs kontrollja (nem lehet pl. prioritást szabályozni)
V1.0
2008. március 10.
© Miklós Árpád, BMF NIK, 2007
[email protected]
32
Szálak kezelése (kivonatos referencia) • System.Threading.ThreadPool osztály Metódusok QueueUserWorkItem()
Metódus végrehajtása egy ThreadPool szálon A rendelkezésre álló ThreadPool szálak számának GetAvailableThreads() lekérdezése GetMaxThreads(), Maximálisan rendelkezésre álló, illetve minimálisan GetMinThreads() életben tartott ThreadPool szálak számának lekérdezése SetMaxThreads(), Maximálisan rendelkezésre álló, illetve minimálisan SetMinThreads() életben tartott ThreadPool szálak számának beállítása RegisterWaitForSingleObject() Várakozás erőforrásra vagy időzítőre
V1.0
2008. március 10.
© Miklós Árpád, BMF NIK, 2007
[email protected]
33
ThreadPool osztály public sealed class ThreadPool { public static void GetAvailableThreads(out int w, out int aIOs); public static void GetMaxThreads(out int w, out int aIOs);
public static bool QueueUserWorkItem( WaitCallback task); public static bool QueueUserWorkItem( WaitCallback task, object state);
• A munka és IO szálak száma • A munka és IO szálak maximális száma • A task regisztrálása WaitCallback delegateként
}
public delegate void WaitCallback(object state );
V1.0
2008. március 10.
© Miklós Árpád, BMF NIK, 2007
[email protected]
• WaitCallback delegate
34
ThreadPool – példaváz • Feladat definíció public static void WorkerTask(object state) { while (…) { … // do something short Thread.Sleep(…); // then sleep } }
• A munka és IO szálak száma int maxWorkers, availWorkers; int maxIOs, availIOs; ThreadPool.GetMaxThreads(out maxWorkers, out maxIOs); ThreadPool.GetAvailableThreads(out availWorkers, out availIOs);
• Új feladatot adunk a poolba object state = …; ThreadPool.QueueUserWorkItem(new WaitCallback(WorkerTask), state); V1.0
2008. március 10.
© Miklós Árpád, BMF NIK, 2007
[email protected]
35
Feladat (3) Készítsünk konzolos alkalmazást, amely a ThreadPool osztály segítségével 4 külön szálon jeleníti meg az egyes szálak által folyamatosan növelt saját belső számláló értékét! A program valamilyen megoldással biztosítsa a szálak által kiírt adatok vizuális elkülönítését! Ötletek: – A QueueUserWorkItem() metódus paramétere egy WaitCallback típusú képviselő, amely visszatérési érték nélküli, egyetlen („object” típusú) paraméterrel rendelkező metódusokat képes tárolni – A ThreadPool szálai is azonosíthatók a ManagedThreadID tulajdonsággal (azonban előfordulhat például, hogy egy szál által megkezdett feladatot egy másik szál folytat és egy harmadik szál fejez be) – A szálak közötti váltások megfigyeléséhez érdemes sok munkát adni az egyes szálaknak és néha várakoztatni őket (ennek legegyszerűbb módja a Thread osztály statikus Sleep() metódusa) V1.0
2008. március 10.
© Miklós Árpád, BMF NIK, 2007
[email protected]
36
Megoldás (3)
ThreadExamples\Program.cs V1.0
2008. március 10.
© Miklós Árpád, BMF NIK, 2007
[email protected]
37
Szinkronizáció • A szinkronizáció olyan, párhuzamos szálak (vagy folyamatok) együttműködését megvalósító mechanizmus, amely minden körülmények között biztosítja a szálak (vagy folyamatok) által végzett műveletek szemantikai helyességét A párhuzamosan futó szálak kommunikációjához szinte biztosan szükség van közös erőforrások (memória, 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.
• Az alapprobléma: bármely két utasítás végrehajtása között előfordulhat, hogy más szálak kapnak lehetőséget az előző szál által is kezelt közös adatok olvasására vagy módosítására Egyprocesszoros rendszereknél az operációs rendszer ütemezője (a szálak közötti váltás) ad erre lehetőséget, többprocesszoros rendszereknél pedig a valódi (fizikai) párhuzamosság miatt még gyakrabban merül fel a probléma. Ennek elkerülését szolgálják elsősorban a különböző szinkronizációs megoldások (másik, ezzel összefüggő céljuk az időzítések összehangolása). V1.0
2008. március 10.
© Miklós Árpád, BMF NIK, 2007
[email protected]
38
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. .NET osztályok: System.Threading.Monitor (és a C# „lock” utasítása), System.Threading.Mutex, System.Threading.ReaderWriterLock
• 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 (.NET 2.0)
• 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”) Olvasható és írható FIFO puffer, amely szükség szerint várakoztatja az igénylőket (az ún. „termelő‐fogyasztó” probléma megoldására készült). V1.0
2008. március 10.
© Miklós Árpád, BMF NIK, 2007
[email protected]
39
Szinkronizáció bevárással (randevú) • Esemény („event”) Két kódrészlet soros végrehajtását biztosítja úgy, hogy a „B” kódrészletet végrehajtó szál megvárja, amíg az „A” kódrészletet végrehajtó szál végez feladatával, illetve lehetőséget ad alkalmankénti vagy rendszeres jelzésre is. .NET osztályok: System.Threading.Thread, System.Threading.AutoResetEvent, System.Threading.ManualResetEvent
• 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 Időzítők jellemzői Windows.Forms.Timer Pontosság ~10 ms Futtatás saját szálon – Csak egyszeri aktiválás – Beállítható első aktiválás – Vizuális komponens + Platformfüggetlen – V1.0
2008. március 10.
© Miklós Árpád, BMF NIK, 2007
[email protected]
Timers.Timer ~100 ns (!) + + – – –
Threading.Timer ~1 ms + + + – + 40
Szinkronizációs elemek I. • Egyszerű blokkoló metódusok * Konstrukció Sleep Join
Cél Adott ideig blokkol Másik szál végét várja
• Lock‐koló konstrukciók, kritikus szakasz * Konstrukció lock Monitor (Enter(), Exit()) Mutex Semaphore * V1.0
Cél Csak egy szál férhet az erőforráshoz, vagy a kódrészhez Csak egy szál férhet az erőforráshoz, vagy a kódrészhez Csak egy szál férhet az erőforráshoz, vagy a kódrészhez. Meghatározott számú szál férhet az erőforráshoz, vagy a kódrészhez
Cross process
Sebesség
–
+
–
+
+
–
+
–
Ha a szálnak várakoznia kell ezen konstrukciók miatt, akkor un. blokkolt és állapota: WaitSleepJoin. Ilyenkor nem kap ütemezést! 2008. március 10.
© Miklós Árpád, BMF NIK, 2007
[email protected]
41
Szinkronizációs elemek II. • Szignál konstrukciók * Konstrukció EventWaitHandle Wait, Pulse ** * **
Cél Engedélyezi, hogy a szál várjon, amíg egy másik száltól szignált nem kap Engedélyezi, hogy a szál várjon, amíg egy beállított blokkoló feltétel teljesül
Cross process
Sebesség
+
–
–
–
Ha a szálnak várakoznia kell ezen konstrukciók miatt, akkor un. blokkolt és állapota: WaitSleepJoin. Ilyenkor nem kap ütemezést! A Pulse egyirányú kommunikáció: nincs visszajelzés. Azt sem tudhatjuk, hogy a Wait‐nél mennyivel később szabadul fel a blokkolás. Használjunk jelző flag‐et!
• Nem‐blokkoló szinkronizációs elemek Konstrukció Interlocked volatile V1.0
2008. március 10.
Cél Nem blokkoló atomi végrehajtást biztosít Lokkon kívül, biztonságos, nem blokkoló hozzáférést biztosít bizonyos mezőkhöz © Miklós Árpád, BMF NIK, 2007
[email protected]
Cross process
Sebesség ++ ++ 42
Kölcsönös kizárás lock utasítás lock(Variable) Statement
Példa: class Account { long val = 0; public void Deposit(long x) { lock (this) { val += x; } } public void Withdraw(long x) { lock (this) { val -= x; } } }
// this class is a monitor
// only 1 thread at a time may execute this statement
A Lock bármilyen objektumra állítható, gyakran – hibásan – a this‐t használják
V1.0
private static object staticSynchRoot = new object(); private object instanceSynchRoot = new object(); ... public static void StaticFunction() { lock (staticSynchRoot ) { ... critical region ... } } public void InstanceFunction() { lock (instanceSynchRoot ) { ... critical region ... } } © Miklós Árpád, BMF NIK, 2007
2008. március 10.
[email protected]
43
Példa szinkronizációra (a „lock” utasítás) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 V1.0
using System; using System.Threading; class Program { private static int counter = 0; private static object lockObject = new Object();
Figyelem: SOHA ne írjunk le az alábbiakra hasonlító kódot:
lock (this)
static void Main(string[] args) { Thread t1 = new Thread(ThreadMethod); t1.Start(); Thread t2 = new Thread(ThreadMethod); t2.Start(); }
vagy
lock (typeof(Program))
A lock utasítás nélkül a metódus
}
private static void ThreadMethod() sorosan (egy szálon futtatva) { helyesen működik, párhuzamosan lock (lockObject) (több szálon) azonban nem { counter++; Thread.Sleep(500); Console.WriteLine("A számláló állása: " + counter); } } 2008. március 10.
© Miklós Árpád, BMF NIK, 2007
[email protected]
44
A szinkronizáció két alapvető nehézsége • Versenyhelyzet („race condition”) Párhuzamos futtatás esetén a közösen használt erőforrásokhoz történő hozzáférés szabályozatlansága veszélyezteti a program helyes működését. Az előző példa a lock utasítás nélkül jól illusztrálja a versenyhelyzet fogalmát.
• Holtpont („deadlock”) Akkor léphet fel holtpont, ha több szál több erőforráshoz kíván hozzáférni, miközben egyes erőforrásokat lefoglalva tartanak (tehát már beléptek egy erőforráshoz tartozó kritikus szakaszba, ott viszont várakozniuk kell, hogy hozzájuthassanak egy másik szükséges erőforráshoz). Példa: 1 lock (a) 2 { // feldolgozás 3 lock (b) 4 { 5 // feldolgozás 6 } 7 8 } V1.0
2008. március 10.
1. szál
1 lock (b) 2 { // feldolgozás 3 lock (a) 4 { 5 // feldolgozás 6 } 7 8 }
© Miklós Árpád, BMF NIK, 2007
[email protected]
2. szál
45
Monitor osztály • A Monitor osztály alapszintű szinkronizációt biztosít public sealed class Monitor { public static void Enter(object obj); public static bool TryEnter(object obj); public static void Exit(object obj); public static void Wait(object obj); public static bool Pulse(object obj); public static void PulseAll(object obj); }
• Az obj részére lock‐kolást próbál biztosítani és blokkol • Az obj részére lock‐kolást próbál biztosítani és visszatér • Feloldja a lock‐kolást • Várakozás állapotba hozza a szálat és elengedi a lock‐ot • Felébreszti a következő obj‐ra váró szálat • Felébreszti az összes obj‐ra váró szálat
• lock a Monitor rövid formája: lock (obj) { … } V1.0
2008. március 10.
Î
Monitor.Enter(obj) try { … } finally { Monitor.Exit(obj) }
© Miklós Árpád, BMF NIK, 2007
[email protected]
46
Monitor használata Enter blokkol, ha a lock‐kolás nem biztosítható TryEnter blokkolás nélkül próbál lock‐kolni; hamissal tér vissza, ha nem elérhető a lock‐kolás
• •
Enter: blokkolással!
TryEnter: blokkolás nélkül
public class MonitorExample { private Queue lpt;
public bool AddElemNonBlocking (object elem) { try { if (! Monitor.TryEnter (lpt.SyncRoot)) return false; lpt.Enqueue (elem); } catch (Exception e) { … } finally { Monitor.Exit (lpt.SyncRoot); } return true; }
public void AddElemBlocking (object elem) { try { Monitor.Enter (lpt.SyncRoot); lpt.Enqueue (elem); } catch (Exception e) { … } finally { Monitor.Exit (lpt.SyncRoot); } } V1.0
2008. március 10.
}
© Miklós Árpád, BMF NIK, 2007
[email protected]
47
Wait és Pulse • A Wait és Pulse segítségével a szálak szinkronizálhatók • A lock‐kolás elengedése és várakozás ébredésre public static void Wait(object obj); public static bool Wait(object obj, int millies);
Az obj‐re váró következő, vagy összes szál felébresztése public static bool Pulse(object obj); public static void PulseAll(object obj);
lock (obj) { ... Monitor.Wait(obj); ... }
V1.0
2008. március 10.
lock (obj) { ... Monitor.Pulse(obj); ... }
© Miklós Árpád, BMF NIK, 2007
[email protected]
48
Wait és Pulse – elvi példa Thread A lock(v) { ... Monitor.Wait(v); 1 ... } 2 5
Thread B lock(v) { ... Monitor.Pulse(v); 3 ... } 4
1. A eléri lock(v)‐t és lock‐kol, mert a kritikus régió szabad 6 2. A eléri Wait utasítást, aludni megy és felengedi a lock‐ot 3. B eléri lock(v)‐t és lock‐kol, mert a kritikus régió szabad 4. B eléri a Pulse‐t és felébreszti A‐t. (Lehetséges kontextusváltás A és B között, de nem szüségszerű) 5. A lock‐ot szeretne kapni, de nem tud, mert B még kritikus régióban van 6. A kritikus régió végén B felengedi a lock‐ot; A tovább futhat
V1.0
2008. március 10.
© Miklós Árpád, BMF NIK, 2007
[email protected]
49
Wait és Pulse • Megjegyzés •
Wait(v) és Pulse(v) csak lock(v)‐vel (Monitorral) zárolt utasításban jelenhet meg.
•
A Pulse(v) és a felébresztett szál folytatása között más szálak futhatnak, amelyek időközben megpróbálták megkapni a lock‐ot (tehát a Pulse által szignált feltétel nem biztosan igaz, amikor a felébresztett szál visszatér a Wait után!) Ezért a Wait metódust egy ciklusba kell helyezni, ami folyamatosan teszteli a feltételt: while (condition false) Monitor.Wait(v); ... make condition true; Monitor.Pulse(v);
•
V1.0
PulseAll(v) felébreszt minden szálat, amely v‐re vár, de közülük csak egy folytatja futását. A többinek várnia kell, amíg a lock‐ot fel nem engedi az előző szál. Ekkor a következő szál a kritikus régióba léphet.
2008. március 10.
© Miklós Árpád, BMF NIK, 2007
[email protected]
50
Wait és Pulse – szinkronizált puffer példa Ha a termelő gyorsabb: Put Put Put Get Put Get ...
public class Buffer { const int size = 16; char[ ] buf = new char[size]; int head = 0, tail = 0, n = 0; public void Put(char ch) { lock(this) { while (n == size) Monitor.Wait(this); buf[tail] = ch; tail = (tail + 1) % size; n++; Monitor.Pulse(this); } } public char Get() { lock(this) { while (n == 0) Monitor.Wait(this); char ch = buf[head]; head = (head + 1) % size; n--; Monitor.Pulse(this); return ch; } }
Wake up waiting threads
thread 2
Lock buffer to retrieve character While buffer is empty, release lock and wait Wake up waiting threads
}
V1.0
2008. március 10.
thread 1
Lock buffer to add a character While buffer is full, release lock and wait
© Miklós Árpád, BMF NIK, 2007
[email protected]
Ha a fogyasztó a lassabb: Put Get Put Get ...
51
Szinkronizált puffer 3 Get szál eléri az üres puffert
Belépnek a kritikus régióba és aludni mennek, mert üres a puffer
Egy Put szál érkezik, Kritikus régióba lép; Elhelyezi adatát és jelez: PulseAll
G1 G2
P1
G3
Első belépés
Kritikus régió
G1 G2 G3
G1 G2 G3
Várakozás Mindenl Get szál felébred; Az első a kritikus régióba lép, kiolvassa az adatot és kilép
G1 G2 G3
Ismételt belépés V1.0
2008. március 10.
A többiek ismét várakozási állapotba kerülnek, mert üres a puffer
G2 G3 © Miklós Árpád, BMF NIK, 2007
[email protected]
52
Feladat (4) Készítsünk többszálú konzolos alkalmazást, amely egy időigényes számítási műveletet 2 szállal párhuzamosan végeztet el! A műveletet most egy közösen használt számláló folyamatos növelése jelentse, és egy szálnak kb. 2‐3 másodpercig tartson a művelet elvégzése! Amennyiben szükséges, gondoskodjon a szálak szinkronizációjáról is! A program valamilyen megoldással biztosítsa a szálak által kiírt adatok vizuális elkülönítését! Ötletek: – A Stopwatch osztály metódusai segítségével egyszerűen mérhető az eltelt (relatív) idő – 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 V1.0
2008. március 10.
© Miklós Árpád, BMF NIK, 2007
[email protected]
53
Feladat (5) Készítsünk konzolos alkalmazást, amelynek csak egy példánya futhat egyszerre. Használjunk Mutexet a megvalósításhoz! (Készítsünk el ugyanilyen funkciójú alkalmazást processzek lekérdezésével és vizsgálatával is!) Ötletek: – A Mutex name paramétere egyedi legyen. Pl.”bmfnik.hu MUTEXv01” – A Mutex WaitOne és ReleaseMutex metódusait használjuk. Az első metódus várakozási paramétert is kaphat, amit felhasználva megjeleníthetünk pl. hibaüzenetet. – Processzek esetében a GetCurrentProcess és a GetProcesses metódusok segíthetnek.
V1.0
2008. március 10.
© Miklós Árpád, BMF NIK, 2007
[email protected]
54
Feladat (6) Készítsünk konzolos alkalmazást, amely Semaphore segítségével egy garázs működését prezentálja. A garázs kapacitása 10, benne a főnököknek foglalt helyek száma 5. A kocsik (szálak) véletlen ideig állnak a garázsban és összesen 20 tulajdonos jogosult a használatra. Az érkezéskor és induláskor írjuk ki, hogy melyik autó óhajt beállni, illetve elmenni. Ötletek: – Osztályszintű Semaphore WaitOne, illetve Release metódusát használjuk. – A szálfüggvény paraméterként kapja meg, hogy mely autó szeretne beállni. – A parkolást Sleep metódussal valósítsuk meg.
V1.0
2008. március 10.
© Miklós Árpád, BMF NIK, 2007
[email protected]
55
Feladatok (7) Készítsünk konzolos alkalmazást, amely az Interlocked osztály atomi metódusait teszteli! Ötletek: – Egy utasítás atomi, ha egyetlen oszthatatlan instrukcióként fut. Az egyszerű 32 bites adatok olvasása (32 bites CPU‐n) atomi. Olvasást és írást kombináló utasítások nem atomiak (pl. az x++ nem atomi). – Az Interlocked statikus metódusai nem blokkolnak és sosem kell az ütemező átütemezésére várniuk (szemben pl. a lock‐kal). – Az Interlocked osztály atomi operációkat biztosít ref paraméterként átadott int, vagy long típusokra. – Teszteljük az Increment, Decrement, Add, Read, Exchange és CompareExchange metódusok működését egy long típusú ref paraméter használatával.
V1.0
2008. március 10.
© Miklós Árpád, BMF NIK, 2007
[email protected]
56
Feladatok (8) Készítsünk konzolos alkalmazást, amelynek elindított mindkét szálja egymásnak ötször üzenetet küld. Az üzenet megérkezésekor az egyik szál azt írja ki a konzolra: ThreadPing, a másik ThreadPong! Ötletek: – Használjuk a Monitor osztály Pulse és Wait metódusait osztályszintű lock objektummal (static object ball).
V1.0
2008. március 10.
© Miklós Árpád, BMF NIK, 2007
[email protected]
57
Feladatok (9) Készítsünk demonstrációs alkalmazást, amely az AutoResetEvent és a ManualResetEvent működése közti különbséget mutatja be! Az elindított szál küldjön szignált a főszálban adott ideig, többször várakozó ResetEventeknek. Írjuk ki, hogy jel hatására milyen állapotba kerültek.
Ötletek: – Használjuk az osztályok Set és WaitOne és Reset metódusait. – A szálfüggvényben Sleeppel „szabályozzunk”.
V1.0
2008. március 10.
© Miklós Árpád, BMF NIK, 2007
[email protected]
58
Feladatok (11) Töltsünk fel stringlistát két szál segítségével 100‐100 elemmel és az eredményt jelenítsük meg képernyőn. Ötletek: – Használjuk a Collections névtér List generikus típusát: List<string> list = new List<string>(), illetve az Add metódust.
Két szál AutoResetEventekkel kommunikáljon egymással, amíg az egyik speciális üzenetet nem küld. Az üzenet osztályszintű string legyen. Ötletek: – Hogyan védjük az üzenetet?
V1.0
2008. március 10.
© Miklós Árpád, BMF NIK, 2007
[email protected]
59
BackgoundWorker szálak (rövid referencia) • System.ComponentModel.BackgroundWorker osztály Metódusok RunWorkerAsync() CancelAsync() ReportProgress Tulajdonságok IsBusy CancellationPending WorkerSupportsCancellation WorkerReportsProgress Események DoWork ProgressChanged RunWorkerCompleted
*
V1.0
Háttérszál indítása Háttérszál leállítása Háttérszál folyamatjelzése A háttérszál aktív‐e (éppen fut‐e) Leállítás folyamatban (leállási kérelem érkezett) A háttérszál kérés esetén képes idő előtti leállásra A háttérszál képes folyamatjelzésre Kezelője a háttérben futtatandó metódus* A háttérszál folyamatjelzését fogadó esemény A háttérszál futása befejeződött
Ez a metódus (a Windows UI megvalósítási modellje következtében) közvetlenül nem érintkezhet a felhasználói felület elemeivel. Ezek szükséges frissítését és állapotmódosításait a ProgressChanged és a RunWorkerCompleted eseménykezelőkben lehet elvégezni. 2008. március 10.
© Miklós Árpád, BMF NIK, 2007
[email protected]
60
Feladat (12) Készítsünk Windows alkalmazást, amely időigényes műveletet futtat a háttérben, a felhasználói felülettől független szálon! A program legyen képes a művelet indítására és menet közbeni biztonságos megszakítására, a háttérben futó művelet állapotát pedig folyamatjelzővel jelezze! Ötletek: – Célszerű a BackgroundWorker osztály segítségével megoldani a feladatot – A háttérben futó szál a ReportProgress() metódussal jelezheti az előrehaladást (ezt az adatot a ProgressChanged esemény második paraméterében kapja meg a megfelelő eseménykezelő metódus) – A művelet megszakítását csak akkor kíséreljük meg, ha valóban fut (ez az IsBusy tulajdonság vizsgálatával állapítható meg) – A háttérben futó művelet végén a RunWorkerCompleted esemény kezelője a második paraméterben kap információt a műveletről (véget ért‐e vagy megszakítás miatt fejeződött be, mi a végeredmény, történt‐e hiba stb.) V1.0
2008. március 10.
© Miklós Árpád, BMF NIK, 2007
[email protected]
61
Megoldás (4)
BackgroundWorker V1.0
2008. március 10.
© Miklós Árpád, BMF NIK, 2007
[email protected]
62
Irodalomjegyzék (alapismeretek) • C. Nagel, B. Evjen, J. Glynn, M. Skinner, K. Watson, A. Jones: Professional C# 2005 Kiadó: Wiley Publishing, Inc., 2006 ISBN: 978‐0‐7645‐7534‐1 Web: http://www.wiley.com/, http://www.wrox.com/ Nyelv: angol Terjedelem: 1540 oldal
Folyamatok kezelése: 14–16., 413. o. Szálkezelés: 349–368. o.
• Microsoft Corp., Visual Studio Developer Center Szálkezelés a .NET keretrendszerben: http://msdn2.microsoft.com/en‐us/library/3e8s7xdd(VS.80).aspx
V1.0
2008. március 10.
© Miklós Árpád, BMF NIK, 2007
[email protected]
63
Irodalomjegyzék (magasszintű ismeretek) • Albert I., Balássy Gy., Charaf H., Erdélyi T., Horváth Á., Levendovszky T., Péteri Sz., Rajacsics T.: A .NET Framework és programozása Kiadó: Szak Kiadó, 2004 ISBN: 963‐9131‐62‐8 Web: http://www.szak.hu/ Nyelv: magyar Terjedelem: 868 oldal
Párhuzamos programozás: 585–607. o. Folyamatok kezelése: 608–613. o. Szálkezelés és szinkronizáció: 614–648. o.
• J. Richter: CLR via C#, Second Edition Kiadó: Microsoft Press, 2006 ISBN: 978‐0‐7356‐2163‐3 Web: http://www.wintellect.com/ Nyelv: angol Terjedelem: 736 oldal
Szálkezelés és szinkronizáció: 585–648. o. V1.0
2008. március 10.
© Miklós Árpád, BMF NIK, 2007
[email protected]
64