200 likes | 395 Views
SVEUČILIŠTE U ZAGREBU FAKULTET ELEKTROTEHNIKE I RAČUNARSTVA Zavod za primijenjen o računarstvo. Programske paradigme i jezici. 8. auditorne vježbe (Multithreading). Višenitnost (višedretvenost, eng. multithreading).
E N D
SVEUČILIŠTE U ZAGREBU FAKULTET ELEKTROTEHNIKE I RAČUNARSTVA Zavod za primijenjenoračunarstvo Programske paradigme i jezici 8. auditorne vježbe (Multithreading)
Višenitnost (višedretvenost, eng. multithreading) • Višenitni program sastoji se od 2 ili više dijelova koji se mogu istovremeno izvršavati (pseudo-paralelizam). Svaki dio takvog programa naziva se nit ili dretva (eng. thread). • Ime dolazi od ‘thread of execution’. Svaki proces ima barem jednu nit. • Primjer: tekst procesor, web server, matrični kalkulator... • Prednost: Bolja iskoristivost procesorskog vremena, posebno kod programa koji imaju sporiji I/O. Progress
Višenitnost • Razredi koji podržavaju višenitno programiranje nalaze se u prostoru imena System.Threading • Kako bi se koristila nit, treba instancirati objekt tipa Thread. • Najčešće korišteni konstruktori razreda Thread public Thread(ThreadStart start) public Thread(ParameterizedThreadStart start) • start je ime postupka koja će biti pozvan kako bi počelo izvršavanje niti. • ThreadStart je delegat (neprecizno rečeno: pokazivač na postupak) • Jednom kreirana nit se pokreće postupkom Start() SimpleThread
Višenitnost (SimpleThread program) using System; using System.Threading; class MojThread{ public int brojac; string imeThreada; public MojThread(string ime){ brojac=0; imeThreada = ime; } //entryPoint , tj. postupak s kojim će se startati thread, //može nositi bilo koje ime, ne može primiti nijedan parametar* public void run(){ Console.WriteLine(imeThreada + " startan"); do { Thread.Sleep(500); Console.WriteLine("\nUnutar " + imeThreada + ", brojac=" + brojac); brojac++; } while(brojac<5); Console.WriteLine(imeThreada + " završio"); } *ParameterizedThreadStart može imati 1 parametar
Višenitnost (SimpleThread program) class Glavni { public static void Main() { Console.WriteLine("Glavni thread startan."); //instanciramo MojThread objekte MojThread mt1 = new MojThread("PPJ-#1"); MojThread mt2 = new MojThread("PPJ-#2"); //konstruiramo Thread objekte Thread thread1 = new Thread(new ThreadStart(mt1.run)); Thread thread2 = new Thread(new ThreadStart(mt2.run)); //startamo threadove thread1.Start();thread2.Start(); do { Console.Write("."); Thread.Sleep(100); } while (mt1.brojac<5 && mt2.brojac<5); Console.WriteLine("Kraj glavnog threada"); } }
Višenitnost • Statička metoda Sleep uzrokoje privremeno zaustavljanje izvršavanja niti iz koje je pozvana za navedeni broj milisekundi. • Načinimo neke promjene u prethodnom programu • Neka se nit pokreće odmah pri instanciranju. Ovo ćemo napraviti instancirajući Thread unutar konstruktora razreda MojThread. • Svaki Thread objekt ima svojstvo Name što možemo koristiti umjesto imeThread. • Neka glavni program koristi neki drugi način utvrđivanja kraja pokrenutih niti pomoću metode IsAlive • public bool IsAlive {get; } SimpleThread2
Višenitnost (SimpleThread2 program) using System; using System.Threading; class MojThread{ public int brojac; public Thread thread; public MojThread(string ime) { brojac=0; thread = new Thread(new ThreadStart(this.run)); thread.Name = ime; thread.Start(); } public void run(){ Console.WriteLine(thread.Name + " startan"); do { Thread.Sleep(500); Console.WriteLine("\nUnutar " + thread.Name + ", brojac=" + brojac); brojac++; } while(brojac<5); Console.WriteLine(thread.Name + " završio"); }
Višenitnost (SimpleThread2 program) class Glavni { public static void Main() { Console.WriteLine("Glavni thread startan."); //instanciramo MojThread objekte MojThread mt1 = new MojThread("PPJ-#1"); MojThread mt2 = new MojThread("PPJ-#2"); do { Console.Write("."); Thread.Sleep(100); } while (mt1.thread.IsAlive && mt2.thread.IsAlive); Console.WriteLine("Kraj glavnog threada"); } } } • Drugi način čekanja na završetak pojedine niti je Join postupak. Najjednostavniji oblik je public void Join() • Join čeka dok se nit na kojoj je pozvan ne završi. U slučaju da nit nije pokrenuta dogodit će se iznimka: ThreadStateException mt1.thread.Join(); mt2.thread.Join(); Join uzrokuje blokiranje niti dok nit na koju se čeka ne završi.
Višenitnost (prioriteti) • Postavljanje prioriteta pojedine niti obavlja se preko svojstva Priority koji je član razreda Thread. Njegov opći oblik je public ThreadPriority Priority{ get; set; } • ThreadPriorityje pobrojani tip koji definira sljedeće prioritete: • ThreadPriority.Highest • ThreadPriority.AboveNormal • ThreadPriority.Normal (inicijalna vrijednost) • ThreadPriority.BelowNormal • ThreadPriority.Lowest • Sljedeći primjer kreira dvije niti, jednu višeg prioriteta, a drugu nižeg koje završavaju onog trenutka kada brojač u jednoj od njih dođe do 1 000 000 000. PriorityDemo
Višenitnost (PriorityDemo) using System; using System.Threading; class MojThread{ public int brojac; public Thread thread; static bool stop=false; static string trenutnoIme; public MojThread(string ime){ brojac=0; thread = new Thread(new ThreadStart(this.run)); thread.Name = ime; trenutnoIme = ime; } public void run(){ Console.WriteLine(thread.Name + " startan"); do{ brojac++; if (trenutnoIme!=thread.Name){ trenutnoIme = thread.Name; Console.WriteLine(" Aktivan: " + trenutnoIme); } }while(stop==false && brojac<1000000000); stop = true; Console.WriteLine(thread.Name + " završio"); } }
Višenitnost (PriorityDemo) class PriorityDemo { public static void Main() { Console.WriteLine("Glavni thread startan."); //instanciramo MojThread objekte MojThread mt1 = new MojThread("Viseg Prioriteta"); MojThread mt2 = new MojThread("Nizeg Prioriteta"); //postavimo prioritete mt1.thread.Priority = ThreadPriority.AboveNormal; mt2.thread.Priority = ThreadPriority.BelowNormal; //pokrenimo threadove mt1.thread.Start(); mt2.thread.Start(); mt1.thread.Join(); mt2.thread.Join(); Console.WriteLine("Brojac u " + mt1.thread.Name + "je dosao do" + mt1.brojac); Console.WriteLine("Brojac u " + mt2.thread.Name + "je dosao do" + mt2.brojac); } }
Višenitnost (stanja niti) • Stanje niti može se dobiti iz svojstva ThreadState koji se nalazi unutar razreda Thread: public ThreadState ThreadState {get; } • Stanje može poprimiti jednu od sljedećih vrijednosti: ThreadState.Aborted ThreadState.AbortRequested ThreadState.Background ThreadState.Running ThreadState.Stopped ThreadState.StopRequested ThreadState.Suspended ThreadState.SuspendRequested ThreadState.Unstarted ThreadState.WaitSleepJoin • Referencu na trenutno nit možemo dobiti pomoću svojstva CurrentThread
Višenitnost (Suspend, Resume, Abort) • Thread.Suspend() – privremeno zaustavlja izvršavanje niti • Thread.Resume() – ponovo pokreće privremeno zaustavljenu nit • Thread.Abort() – uzrokuje ThreadAbortException na niti na kojoj se izvrši, što uzrokuje završetak niti • Napomena: Ova iznimka može biti uhvaćena, ali se automatski ponovo generira, osim u slučaju ako se pozove metoda ResetAbort() • SuspendResumeAbort
Višenitnost (Suspend, Resume, Abort) using System; using System.Threading; class MojThread{ public Thread thread; public MojThread(string ime){ thread = new Thread(new ThreadStart(this.run)); thread.Name = ime; thread.Start(); } public void run(){ Console.WriteLine(thread.Name + " startan"); for (int i=1; i<=200 ; i++){ Console.Write(i + " "); if ((i%10)==0){ Console.WriteLine(); Thread.Sleep(250); } } Console.WriteLine(thread.Name + " završio"); }
Višenitnost (Suspend, Resume, Abort) class SuspendResumeAbort{ public static void Main(){ MojThread mt1 = new MojThread("Moj Thread"); Thread.Sleep(1000); mt1.thread.Suspend(); Console.WriteLine("Suspending Thread"); Thread.Sleep(1000); mt1.thread.Resume(); Console.WriteLine("Resuming Thread"); Thread.Sleep(1000); mt1.thread.Suspend(); Console.WriteLine("Suspending Thread"); Thread.Sleep(1000); mt1.thread.Resume(); Console.WriteLine("Resuming Thread"); Thread.Sleep(1000); Console.WriteLine("Stopping Thread"); mt1.thread.Abort(); mt1.thread.Join(); Console.WriteLine("Kraj glavnog threada"); } } Uočimo da nit nije ispisala poruku da se normalno završila (dogodila se neuhvaćena iznimka) Kao parametar za Abort možemo unijeti bilo koji objekt.Unesimo npr. neki broj mt1.thread.Abort(100);
Višenitnost (Suspend, Resume, Abort) public void run(){ Console.WriteLine(thread.Name + " startan"); for (int i=1; i<=200 ; i++){ Console.Write(i + " "); if ((i%10)==0){ Console.WriteLine(); Thread.Sleep(250); } } Console.WriteLine(thread.Name + " završio"); } try{ Uhvatimo ThreadAbortException i ispišemo odgovarajuću poruku } catch(ThreadAbortException exc) { Console.WriteLine("Thread aborted, code: " + exc.ExceptionState); } Thread.ResetAbort(); Console.WriteLine("Resetiram abort->nastavljam izvršavanje"); Uočimo poruku
Višenitnost (Sinkronizacija) • Promotrimo sljedeći primjer: Neka su startane 3 niti, i neka svaka od njih treba u zajedničku datoteku zapisati nekoliko rečenica, ali pišući znak po znak. Svaka od niti u svom konstruktoru kao parametar će dobiti listu rečenica (ArrayList- varijabla recenice) koje treba zapisati u datoteku i StreamWriter (varijable writer) pomoću kojeg će vršiti upisivanje u datoteku. Kod koji upisuje u datoteku neka je sljedeći: foreach(String s in recenice){ foreach (char c in s){ Thread.Sleep(10); writer.Write(c); } writer.WriteLine(); } • WriteSentence_NoSync • Pokrenemo li gornji program vidjet ćemo da nam je ispis u datoteci izmiješan. • Zaštitit ćemo pristup pojedinom dijelu koda, kako bi omogućili da rečenice budu pravilno ispisane. • WriteSentence • AccountTransaction lock(writer){ Programski odsječak u kojem se pojedina rečenica zapisuje ogradimo sa lock(writer) i onemogućimo istovremeni ulazak dviju niti u taj odsječak. }
Višenitnost (Sinkronizacija) • Opći oblik lock naredbe je lock(object){ //programski odsječak koji treba sinkronizirati } • lock naredba osigurava da dio programa zaštićen lock-om (lock nad nekim objektom) može biti izvršavan samo onom niti koja je vlasnik tog lock-a. • Ako neka nit ‘zaključa’ object sve ostale niti koje pokušaju ući u neki programski odsječak koji je zaštićen tim istim objektom moraju čekati dok nit ‘vlasnik’ objekta ne napusti zaštićeni odsječak. • lock objekt može biti bilo što (pa i string), ali treba paziti da ne dođe do potpunog zastoja. Najbolje je koristiti privatni objekt namijenjen isključivo za tu svrhu. • lock je zapravo jednostavni oblik korištenja sinkronizacijskih postupaka definiranih u razredu Monitor. Definirano je nekoliko postupaka: Enter,Exit,TryEnter,Wait,Pulse,PulseAll
Višenitnost (Sinkronizacija) • Promotrimo sljedeću situaciju: Neka je nit A pozvala postupak T koji je ušao unutar zaštićenog bloka (ili sa lock ili sa Monitor.Enter) i u nekom trenutku treba pristup resursu R koji privremeno nije dostupan. Što A treba napraviti? Ako A u nekoj petlji bude čekao na resurs R i dalje će držati vlasništvo nad objektom T, sprečavajući neke druge niti da uđu u odsječke zaštićene tim objektom. Rješenje je da A privremeno otpusti vlasništvo nad objektom i omogući nekoj drugoj niti ulazak u kritični odsječak. Kada resurs R postane dostupan, nit A će biti obaviještena i moći će nastaviti svoje izvršavanje uz ponovo zaključavanje objekta. Gornje rješenje omogućuju postupci Wait (čekanje određenog objekta) te Pulse (notificiranje o dostupnosti objekta) • TickTock • Opasnost od potpunog zastoja!
Višenitnost (Sinkronizacija) • Semafori ograničavaju broj niti koje mogu istovremeno pristupiti određenom resursu (kritičnom odsječku) • Ukoliko je trenutna vrijednost semafora nula, niti čekaju dok se vrijednost ne poveća. Ovisno o vrijednosti semafora toliki broj niti dobiva pravo izvršavanja kritičnog odsječka. Redoslijed odabranih nije deterministički. • Najčešći konstruktor: Semaphore(int initialCount, int maximumCount) • Najčešći postupci WaitOne() Release() • Progress