Professional Documents
Culture Documents
Milo Ljumovi Koncept niti u savremenim Windows operativnim sistemima Kreiranje i sinhronizacija niti
ZAVRNI RAD
Podgorica, 2011
Operativni sistemi Predrag Stanii Milo Ljumovi Raunarstvo i informacione tehnologije 2607980210013
Saetak
Na poetku ovog rada konceptualno emo predstaviti niti, zatim dati pregled komandi za korienje niti i zavriti programom koji demonstrira kreiranje, sinhronizaciju i korienje viestrukih niti. Objasniemo kako i kada treba kreirati niti u programima, kako upravljati nitima dok se izvravaju i kako ih sinhronizovati korienjem mutexa, semafora i kritine sekcije.
strana 3
Sadraj
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
3 5 7 8 14 21 32 43 52 53
1. Koncept niti
2. Raspored i sinhronizacija niti 3. Komande vezane za niti 4. Sinhronizacija niti Zakljuak Literatura
strana 4
Uvod
Centralni koncept u svim operativnim sistemima je process, odnosno apstrakcija programa koji se izvrava. Veina modernih raunara moe da obavlja vie stvari (operacija) odjednom. Dok se izvrava korisniki program, raunar moe itati sa vrstog diska, pisati u buffer tampaa, itd. U multitasking sistemu CPU vri brze smjene programa, izvravajui svaki svega nekoliko desetina milisekundi. Dok se, striktno govorei, u jednoj jedinici vremena moe izvravati samo jedan proces na procesoru, operativni sistem brzim smjenama (reda veliine nekoliko milisekundi) stvara utisak da se izvrava vie procesa odjednom ili kako se to drugaije naziva pseudoparalelizam, nasuprot hardverski podranom paralelizmu u multiprocesorskim sistemima, u kojima dvije ili vie CPU-a dijeli zajedniku memoriju.
U tradicionalnim operativnim sistemima, svaki proces posjeduje svoj privatni adresni prostor i jednu nit kontrole, koja se obino naziva primarna ili inicijalna nit. U sutini ovo je definicija procesa. Nerijetko je potrebno imati vie niti u adresnom prostoru procesa koje se izvravaju u kvazi-paralelnom kontekstu, ponaajui se kao da su odvojeni procesi, izuzev zajednikog adresnog prostora.
strana 5
strana 6
Istorijat
Ranije verzije Windows-a, odnosno operativni sistemi mlai od Windows-a 2000, radile su na principu permissive multitasking, odnosno dozvoljene vieprocesne obrade, dok novije verzije, poev od Windows-a 2000 rade na principu preemptive multitasking sistema, odnosno prekidne vieprocesne obrade. U dozvoljenom multitasking sistemu operativni sistem se oslanja na takozvanu fer raspodjelu resursa, odnosno na to da aplikacije redovno predaju kontrolu sistemu, kako bi druge aplikacije imale mogunost da dobiju svoj dio vremena. U prekidnom multitasking sistemu operativni sistem zbirno suspenduje izvravanje niti da bi dao drugim nitima odgovarajui dio vremena za izvravanje. Oigledno, prekidni multitasking sistem ima sposobnost da obezbijedi mnogo lake izvravanje vie zadataka, a uz to i ne zavisi od dobre volje svakog programa ili vjetine programera u predavanju kontrole nad CPU-om.
strana 7
1. Koncept Niti
Niti uvode novinu u modelu procesa u vidu mogunosti izvravanja vie razliitih zadataka odjednom. Imati vie niti koje se izvravaju paralelno u jednom procesu je analogno paralelnom izvravanju vie procesa na jednom raunaru. Dok procesi dijele fiziku memoriju, diskove, tampae i druge resurse, niti dijele adresni prostor procesa, otvorene datoteke i druge privatne resurse jednog procesa. Zato to niti imaju odreena svojstva procesa nazivamo ih jo i lightweight procesima. Izraz multithreading se takoe koristi da opie situaciju u kojoj je dozvoljeno izvravanje vie niti u okviru jednog procesa.
Na slici 1.(a) vidimo 3 tradicionalna procesa. Svaki proces posjeduje svoj privatni adresni prostor i jednu nit izvravanja. Nasuprot tome na slici 1.(b) vidimo jedan proces sa 3 niti. Iako u oba sliaja imamo po 3 niti, na slici 1.(a) svaka nit izvrava se u razliitom adresnom prostoru, dok se na slici 1.(b) sve niti izvravaju u istom adresnom prostoru.
strana 8
Slika 1. (a) Tri procesa, sa jendom niti kontrole. (b) Jedan proces sa tri niti.
Kada se proces sa vie niti izvrava na raunaru sa jednim procesorskim jezgrom niti se stalno, brzo, smjenjuju. Na slici 2 vidimo kako izgleda izvravanje vie programa. Kao to je ve reeno, brzim smjenama izmeu razliitih procesa, operativni sistem stvara iluziju odvojenih sekvencijalnih procesa koji se izvravaju paralelno. Multithreading radi na isti nain. CPU brzo izvrava instrukcije prebacujui se sa jedne niti na drugu, stvarajui iluziju o paralelnom izvravanju. Sa tri niti u kontekstu jednog procesa, ini se da se niti izvravaju paralelno, svaka dobijajui jednu treinu vremena na procesoru.
Slika 2. (a) Izvravanje etiri programa. (b) Konceptualni model etiri nezavisna sekvencijalna procesa. (c) Samo jedan program je aktivan u jednoj jedinici vremena.
strana 9
Trebalo bi uzeti u obzir kreiranje niti svaki put kada program obavlja asinhrone operacije. Programi sa vie prozora, na primjer, imaju mnogo koristi od kreiranja niti za svaki od prozora. Drugi primjer bio bi korienje savremenog programa za obradu teksta, poput Microsoft-ovog Word-a, sa ugraenom automatskom provjerom gramatike. Ovdje je
strana 10
jedna nit (ili vie njih) odgovorna za reagovanje na aktivnosti tastature, druge niti za auriranje teksta na ekranu, ostale niti upravljaju redovnim auriranjem rezervnih verzija radne datoteke, itd. Nit koja je zauzeta provjeravanjem teksta koji je bio zapisan do sad u odnosu na odabrani rjenik ili rjenike, kada rije nije prepoznata, alje poruku da bi obavijestila nit displeja da oznai nepoznatu rije. Bilo koja nit moe kreirati druge niti ili nove procese. Kada program treba da uradi nekoliko stvari odjednom mora da odlui da li da kreira procese ili niti koje bi dijelile posao. Dobra programerska praksa kae da treba izabrati niti kad god je to mogue iz jednostavnog razloga to ih sistem kreira brzo i one veoma lako vre meusobnu interakciju. Kreiranje procesa traje due, poto sistem mora da uita novu, izvrnu, datoteku sa diska. Nasuprot tome, kreiranje novog procesa ima prednosti poto svaki proces dobija svoj privatni adresni prostor, a na taj nain se sprjeava i meusobna interferencija niti. Naravno, pri tom treba biti oprezan zbog moguih problema sinhronizacije, ime emo se detaljnije baviti u etvrtom poglavlju.
Objekti niti
Na nivou operativnog sistema nit je objekat kojeg je kreirao Object Manager. Kao i svi sistemski objekti i nit sadri atribute (podatke) i metode (funkcije). Na slici 3 ematski je prikazan objekat nit i data je lista njegovih atributa i metoda.
strana 11
Slika 3. Objekat nit i njegovi atributi. Veina metoda za niti ima odgovarajue Win32 (WinAPI) funkcije. Kada se pozove SuspendThread, na primjer, Win32 podsistem odgovara pozivom metode Suspend. Drugim rijeima, Win32 programski interfejs za aplikacije (API Application Program Interface) izlae metod Suspend za Win32 aplikacije.
Windows je uvijek titio neke unutranje strukture, kao to su prozori i etke, od direktne manipulacije. Niti koje se izvravaju na korisnikom nivou sistema ne mogu direktno da prouavaju ili modifikuju unutranjost sistemskog objekta. Samo pozivanjem Win32 metode moemo neto uraditi sa objektom. Windows daje identifikacioni kod koji
strana 12
identifikuje objekat, a programer prosljeuje identifikacioni kod funkcijama kojima je potreban. I niti imaju identifikacione kodove, kao i procesi, semafori, datoteke i svi objekti uopte. Samo Object Manager moe da mijenja unutranjost objekta. Funkcija koja kreira nit vraa identifikacioni kod za novi objekat. Sa identifikacionim kodom niti moemo: poveati ili smanjiti prioritet izvravanja niti; pauzirati ili rezimirati nit; zavriti nit; saznati koju je vrijednost vratila nit kada je zavrila izvravanje.
strana 13
Rad sa nitima zahtijeva vie nego da ih samo pokrenemo i zaustavimo. Potrebno je da niti efikasno interaguju, to zahtijeva kontrolu nad vremenom. Kontrola vremena ima dva oblika: prioritet i sinhronizaciju. Prioritet kontrolie koliko esto nit dobija vrijeme na procesoru, dok sinhronizacija regulie niti kada se takmie da dobiju zajednike resurse na korienje i daje sekvencu po kojoj nekoliko niti mora da izvri zadatke u odreenom redosljedu.
Kad jedna nit zavri rad i kada se trai sljedea nit za izvravanje, prednost imaju niti sa visokim prioritetom. Neke aktivnosti, kao to je odgovor na iznenadni gubitak napajanja, uvijek se izvravaju sa vrlo visokim prioritetom. Elementi za obnavljanje sistemskih prekida imaju vii prioritet od korisnikih procesa. Ukratko, svaki proces ima rejting listu prioriteta, a osnovni prioritet niti potie iz procesa koji ih posjeduje.
strana 14
strana 15
strana 16
Da bi odabrao sljedeu nit, dispeer poinje od reda najvieg prioriteta, zatim se sputa nie do ostatka liste. Meutim, red moda ne sadri sve niti u sistemu. Neke niti mogu biti suspendovane ili blokirane. U bilo kom trenutku nit moe biti u jednom od sljedeih est stanja: Ready (spremna), nalazi se u redu eka izvravanje; StandBy (u pripravnosti), sljedea za izvravanje; Running (izvrava se), izvrava se u interakciji sa CPU-om; Waiting (eka), ne izvrava se eka signal za rezimiranje; Transition (prelaz), treba upravo da se izvri poto sistem uita njen kontekst; Terminated (zavrena), zavreno izvravanje, ali objekat nije izbrisan. Kada dispeer odabere spremnu nit iz reda, sistem uitava kontekst (context) u operativnu memoriju. Kontekst ukljuuje niz vrijednosti za mainske registre, kernel stek, blok okruenja niti i korisniki stek u adresnom prostoru procesa niti (ako je dio konteksta bio poslat na disk, nit ulazi u prelazno stanje dok sistem sakuplja djelove). Promjena niti znai uvanje svih djelova konteksta i uitavanje svih djelova sljedee niti. Novouitana nit se izvrava jedan dio vremena, koji je reda 2 106 (u obzir je uzet
strana 17
procesor Intel E6750). Sistem odrava broja koji mjeri tekui dio vremena. Za svaki otkucaj
sata sistem umanjuje vrijednost brojaa za odreenu vrijednost. Kada dostigne nulu, dispeer izvodi promjenu konteksta i postavlja novu nit za izvravanje.
Da bi se niti uopte izvravale, mora se zakazati njihovo izvravanje. Da bi se izvravale bez meusobne interference, potrebno ih je sinhronizovati. Recimo da jedna nit kreira etkicu (Brush), a zatim kreira nekoliko niti koje dijele etkicu i crtaju s njom. Prva nit ne smije unititi etkicu dok ostale niti ne zavre crtanje. Ili, recimo, da jedna nit prihvata ulaz od strane korisnika i upisuje ga u datoteku, dok druga nit ita iz datoteke i obrauje tekst. Nit koja ita ne smije da ita dok nit koja upisuje, pie u datoteku. Obje situacije zahtijevaju sredstva za koordinaciju akcija izmeu nekoliko niti. Jedno rjeenje bilo bi da se kreira globalna Boolean promjenljiva bDone koju jedna nit koristi da signalizira drugoj niti. Nit koja upisuje mogla bi da podesi bDone na TRUE, a nit koja ita mogla bi da izvrava petlju dok ne ustanovi da se indikator bDone promijenio. Ovakav mehanizam bi radio, ali nit koja izvrava petlju troi mnogo procesorskog vremena. Umjesto toga Windows operativni sistem podrava niz sinhronizacionih objekata: Objekat mutex (mutex znai mutual exclusion meusobno iskljuenje) radi kao uska kapija gdje moe da proe samo jedna nit u jednom trenutku; Objekat semaphore (semafor) radi kao kapija sa vie djelova kroz koju moe proi ogranien broj niti; Objekat event (dogaaj) emituje javni signal koji bilo koja nit koja slua moe da primi (uje); Objekat critical section (kritina sekcija) radi ba kao i mutex, ali samo u okviru jednog procesa.
strana 18
Svi gore navedeni objekti su sistemski objekti, koje je kreirao Object Manager. Iako svaki sinhronizacioni objekat koordinira razliite interakcije, svi oni rade na slian nain. Nit koja eli da izvri neku koordiniranu operaciju, eka odgovor od jednog od ovih objekata i nastavlja tek kada primi isti. Dispeer uklanja iz reda objekte koji ekaju da ne bi troili procesorsko vrijeme. Kada signal stigne, dispeer dozvoljava rezimiranje niti. Kako i kada signal stie zavisi od objekta. Na primjer, jedna bitna karakteristika mutexa je ta da samo jedna nit moe da ga posjeduje. Mutex ne radi nita, osim to dozvoljava da ga posjeduje samo jedna nit u jednom trenutku. Ako nekoliko niti treba da radi sa jednom datotekom, moemo kreirati mutex kako bi zatitili datoteku. Kada bilo koja nit zapone operaciju nad datotekom, prvo zatrai mutex, i ako ni jedna druga ne dri mutex, nit nastavlja rad. Ako je s druge strane, neka druga nit upravo ugrabila mutex za sebe, zahtjev ne uspijeva, nit se blokira i postaje suspendovana dok eka da preuzme vlasnitvo nad mutexom. Kada jedna nit zavri upisivanje, ona oslobaa mutex i nit koja eka oivljava ponovo, prima mutex i izvodi svoje operacije nad datotekom. Mutex ne titi nita aktivno. On radi samo zato to se niti koje ga koriste slau da ne upisuju u datoteku bez prethodnog posjedovanja istog i ne postoji nita to bi sprijeilo da sve niti pokuaju da upisuju odjednom. Mutex je samo signal, neto kao i Boolean promjenljiva bDone u primjeru sa petljom. Mutex se moe koristiti da bi se zatitile globalne promjenljive, hardverski port, identifikacioni kod za kanal (pipe) ili klijentsku oblast prozora. Svaki put kada vie niti dijeli neki sistemski resurs, treba uzeti u obzir sinhronizaciju istih, kako ne bi dolazilo do meusobnih konflikata. Mutexi, semafori i dogaaji mogu da koordiniraju niti u razliitim procesima, ali objekat kritina sekcija je vidljiv samo nitima u jednom procesu. Kada jedan proces kreira podproces (child process), podproces esto nasljeuje identifikacione kodove za postojee sinhronizacione objekte. Objekti kritine sekcije se ne nasljeuju.
strana 19
Sinhronizacioni objekat je, kao i drugi sistemski objekti, struktura podataka. Sinhonizacioni objekti imaju dva stanja: signalizirano i nesignalizirano. Niti vre interakciju sa sinhronizacionim objektima tako to mijenjaju signal ili tako to ekaju signal. Nit koja eka signal je blokirana i ne izvrava se. Kada se signal desi, nit koja eka prima objekat, iskljuuje signal, izvodi neki sinhronizovani zadatak i ponovo ukljuuje signal kada ostavlja objekat. Niti mogu da ekaju i druge objekte osim mutexa, semafora, dogaaja i kritinih sekcija. Nekad ima smisla ekati proces, nit, tajmer ili fajl. Ovi objekti slue i za druge namjene, ali kao i sinhronizacioni objekti posjeduju stanje signala. Procesi i niti signaliziraju kada se zavre. Tajmer objekti signaliziraju kada proe odreeno vrijeme. Fajlovi signaliziraju kada se zavri operacija upisa ili itanja. Loa sinhronizacija dovodi do greaka (bugs). Na primjer, greka prekida deava se kada dvije niti ekaju jedna drugu. Nijedna od njih nee se zavriti dok se ona druga ne zavri prva. Stanje trke se deava kada program ne uspije da sinhronizuje svoje niti. Recimo da jedna nit upisuje u fajl a druga nit ita novi sadraj. Da li program radi, zavisi od toga koja nit e pobijediti u trci za I/O operacijama. Ako nit koja ita pokua prva da proita, program nee raditi.
strana 20
Da bi predstavili karakteristike programiranja sa vie niti, objasniemo djelove Win32 API-ja koji su vezani za niti. Na poetku emo objasniti komande za kreiranje i modifikaciju niti, a zatim emo se koncentrisati na komande za sinhronizaciju niti.
Ciklus ivota niti poinje kada pozovemo funkciju CreateThread. Druge funkcije dozvoljavaju da prouavamo nit, da je suspendujemo ili rezimiramo, promijenimo njen prioritet i da je zavrimo.
Kreiranje niti
Bilo koja nit moe kreirati drugu nit pozivanjem funkcije CreateThread, to obezbjeuje sline usluge za nit kakve main ili WinMain funkcije obezbjeuju za cio
strana 21
program. Drugim rijeima, argumenti funkcije CreateThread definiu osobine koje nit treba da ima da bi zapoela ivot primarno; siguronosne privilegije i poetnu funkciju. ivot niti poklapa se sa ivotom njene glavne (main) funkcije. Kada funkcija vrati vrijednost, nit se zavrava. Nit se moe pokrenuti u bilo kojoj funkciji koja prima jedan LPVOID (void*) parametar i vraa DWORD (unsigned long) vrijednost. CreateThread dozvoljava da proslijedimo LPVOID u poetnu funkciju. Ako nekoliko niti izvrava jednu funkciju, moemo svakoj od njih proslijediti razliit argument. Svaka od njih mogla bi da primi pokaziva na drugaije ime fajla, na primjer, ili drugaiji identifikacioni kod objekta koji eka. Funkcija CreateThread zahtijeva est parametara. Slijedi izgled prototipa:
HANDLE WINAPI CreateThread( LPSECURITY_ATTRIBUTES lpsa, DWORD cbStack, // privilegije pristupa // inicijalna veliina steka
LPTHREAD_START_ROUTINE lpStartAddress, // pokaziva na funkciju LPVOID lpvThreadParam, DWORD fdwCreate, LPDWORD lpThreadID );
Prvi parametar ukazuje na strukturu SECURITY_ATTRIBUTES koja utvruje ko smije da dijeli objekat i da li drugi procesi smiju da ga modifikuju. Struktura sadri deskriptor sigurnosti koji dodjeljuje privilegije pristupa za razliite korisnike sistema i grupe korisnika. Veina programera jednostavno prihvata podrazumijevani deskriptor koji dolazi sa tekuim procesom. Takoe, siguronosna struktura sadri indikator nasljeivanja, koji, ako je postavljen na TRUE, dozvoljava bilo kojim podprocesima koji su kreirani da automatski naslijede identifikacioni kod za ovaj objekat.
strana 22
} SECURITY_ATTRIBUTES, *LPSECURITY_ATTRIBUTES;
Ne treba da kreiramo strukturu SECURITY_ATTRIBUTES, osim, ako elimo da nit bude naslijeena. Ako proslijedimo NULL kao prvi parametar za CreateThread, nova nit prima podrazumijevani deskriptor i nee biti naslijeena. Sljedea tri parametra daju novoj niti materijal sa kojim moe da radi. Po podrazumijevanoj vrijednosti, svaka nit prima stek koji je iste veliine kao stek primarne niti. Veliinu moete mijenjati uz pomo drugog parametra, ali ako je kasnije potrebno vie mjesta u steku, sistem ga automatski proiruje. Trei parametar ukazuje na funkciju gdje e se nit startovati, a vrijednost u etvrtom parametru postaje argument koji se prosljeuje poetnoj funkciji. Parametar fdwCreate moe da ima jednu od dvije vrijednosti: 0 (nula) ili CREATE_SUSPENDED. Suspendovana nit ne poinje da se izvrava dok je ne poguramo malo pomou funkcije ResumeThread. Program koji kreira veliki broj niti mogao bi da ih suspenduje, akumulira njihove identifikacione kodove, i kada su spremne, da ih pokrene sve odjednom. Posljednji parametar ukazuje na praznu rije DWORD gdje funkcija CreateThread postavlja broj koji jedinstveno identifikuje nit u sistemu. Nekoliko funkcija zahtijeva da identifikujemo nit po njihovim ID brojevima umjesto po njihovim identifikacionim kodovima. Funkcija CreateThread vraa identifikacioni kod za novu nit. Ako nit nije mogla biti kreirana, identifikacioni kod e biti NULL. Treba znati da e sistem kreirati nit ak i ako su lpStartAddress ili lpvThreadParam nevaee, ili ako ukazuju na nevaee adrese (adrese ili
strana 23
podaci kojima se ne moe pristupiti). U tim sluajevima funkcija CreateThread vraa taan identifikacioni kod, ali se nova nit zavrava momentalno i vraa kod greke. Moemo testirati validnost niti pomou GetExitCodeThread, koja vraa STILL_ACTIVE ako se nit nije zavrila. Osim ako funkciji CreateThread ne damo eksplicitni deskriptor sigurnosti, novi identifikacioni kod dolazi sa punim pravima pristupa novom objektu. U sluaju niti, puni pristup znai da pomou ovog identifikacionog koda moemo da suspendujemo, rezimiramo, zavrimo ili promijenimo prioritet niti. Identifikacioni kod ostaje vaei ak i poto se nit zavrila. Da bi unitili objekat nit, treba da zatvorimo njen identifikacioni kod pozivom funkcije CloseHandle. Ako postoji vie od jednog identifikacionog koda, nit nee biti unitena sve dok posljednji identifikacioni kod ne bude zatvoren. Ako zaboravimo da zatvorimo identifikacioni kod, sistem e to uraditi automatski kada se proces zavri.
Niti visokog prioriteta dobijaju vie procesorskog vremena, zavravaju svoj posao bre i vie reaguju na korisnika. Meutim, ako odredimo da sve niti budu najvieg prioriteta, gubi se smisao davanja prioriteta. Ako veliki broj niti ima isti prioritet bilo da je njihov prioritet visok ili nizak dispeer mora da im da isti dio procesorskog vremena. Jedna nit moe vie regovati samo ako druge niti manje reaguju. Isto pravilo se podjednako odnosi i na procese. Sljedee funkcije slue za dobijanje ili modifikovanje osnovnog prioriteta niti:
BOOL WINAPI SetThreadPriority( HANDLE hThread, int nPriority // nit koju treba modifikovati // njen nivo prioriteta
strana 24
);
SetThreadPriority vraa TRUE ili FALSE za uspjeh ili neuspjeh, dok GetThreadPriority vraa tekui prioritet niti. Da bi imenovali mogue vrijednosti prioriteta za obje funkcije, koristimo sljedei niz konstanti:
dva nivoa ispod procesa jedan nivo ispod procesa isti nivo kao kod procesa jedan nivo iznad procesa dva nivoa iznad procesa nivo 15 (u procesima normalnih korisnika) nivo 1 (u procesima normalnih korisnika)
THREAD_PRIORITY_TIME_CRITICAL THREAD_PRIORITY_IDLE -
Prvih pet vrijednosti podeava nivo u odnosu na nivo osnovnog, njenog roditeljskog, procesa, to je ranije prikazano na slici 4. Posljednje dvije, za kritini i neaktivni prioritet, iskazuju apsolutne nivoe prioriteta u viim i niim ekstremima klase prioriteta roditelja (ekstremi za kodove prioriteta u realnom vremenu su 16 i 31). Nivo neaktivnog prioriteta radi dobro za screensaver-e, jer oni ne bi trebalo da se izvravaju osim u sluaju kada se nita drugo ne deava.
Suspendovana nit prestaje da se izvrava i nee se nalaziti u rasporedu za dobijanje procesorskog vremena. Ostaje u ovom stanju dok je druga nit ne primora da se rezimira. Suspendovanje niti moe biti korisno ako, na primjer, korisnik prekida zadatak. Moemo
strana 25
suspendovati nit dok ekamo da korisnik potvrdi otkazivanje. Ako korisnik izabere da nastavi, prekinuta nit se moe rezimirati tamo gdje je stala. Program koji emo predstaviti kasnije suspenduje nekoliko niti za crtanje svaki put kada korisnik mijenja veliinu prozora. Kada se prozor ponovo iscrta (ofarba), niti nastavljaju crtanje. Nit poziva sljedee funkcije da bi natjerala drugu nit da pauzira ili da se rezimira:
Jedna nit se moe suspendovati uzastopno nekoliko puta, bez bilo kakvih komandi za rezimiranje, ali svaka komanda SuspendThread mora nekad da se upari sa komandom ResumeThread. Sistem broji koliko ima komandi za suspenziju za svaku nit (atribut SunspensionCount na slici 4.) . Komanda SuspendThread inkrementira broja, a ResumeThread ga dekrementira, dok obje funkcije vraaju prethodnu vrijednost brojaa u obliku DWORD vrijednosti. Samo kada se broja vrati na nulu, nit nastavlja da se izvrava. Nit moe da suspenduje samu sebe (ali ne moe da rezimira samu sebe) i moe samu sebe da uspava za podeeni period. Komanda Sleep odlae izvravanje, uklanjajui nit iz reda dok ne proe odreeno vrijeme. Interaktivne niti koje piu ili crtaju informacije za korisnika, esto kratko zadrijemaju da bi dale korisniku vrijeme da vidi izlaz. Uspavanost niti je bolja od prazne petlje zato to ne koristi procesorsko vrijeme.
strana 26
Proirena funkcija SleepEx obino radi u konjukciji sa I/O funkcijama u pozadini i moe se koristiti za iniciranje operacije itanja ili upisa bez ekanja da se operacija zavri. Operacija se nastavlja u pozadini. Kada se zavri, sistem obavjetava korisnika tako to pozove povratnu (callback) proceduru iz programa. I/O operacija u pozadini (koja je jo poznata i kao preklapajua I/O operacija) posebno pomae u interaktivnim programima koji moraju stalno da reaguju na korisnika pri radu sa relativno sporim ureajima, kao to su drajveri, mreni diskovi i drugi. Boolean parametar bAlertable u funkciji SleepEx dozvoljava da sistem probudi nit prije vremena ako se preklapajua I/O operacija zavrila prije nego to istekne interval spavanja. Ako je funkcija SleepEx prekinuta, ona vraa WAIT_IO_COMPLETION. Ako interval proe bez prekida, SleepEx vraa nulu.
Nit moe lako dobiti dva dokaza o svom identitetu: identifikacioni kod i identifikator. Sljedee funkcije vraaju informacije koje identifikuju tekuu nit:
Povratna vrijednost iz funkcije GetCurrentThreadId poklapa se sa vrijednou u lpThreadID poslije komande CreateThread. To je vrijednost koja jedinstveno identifikuje nit
strana 27
u sistemu. Iako nekoliko Win32 API komandi zahtijeva da znamo ID niti, to moe biti korisno za praenje niti irom sistema bez potrebe da drimo sve identifikacione kodove otvorene. Treba imati na umu da otvoreni identifikacioni kodovi sprjeavaju da se nit uniti. Identifikacioni kod koji vraa funkcija GetCurrentThread ima istu namjenu kao i identifikacioni kod iz funkcije CreateThread. Iako radi na isti nain kao i drugi identifikacioni kodovi, to je, u stvari, pseudoidentifikacioni kod specijalna konstanta, koju sistem interpretira na odreeni nain - kao to, na primjer, jedna taka (.) uvijek ukazuje na tekui direktorijum, a this u jeziku C++ uvijek ukazuje na tekui objekat. Pseudoidentifikacioni kod koji vraa GetCurrentThread uvijek ukazuje na tekuu nit. Da bi dobila pravi, prenosivi identifikacioni kod za sebe, funkcija DuplicateHandle moe se koristiti na sljedei nain:
// ovdje emo smjestiti duplikat // izvorni proces // originalni identifikacioni kod // odredini proces // novi duplikat identifikacionog koda // prava pristupa (koje je prevaziao // posljednji parametar) FALSE, DUPLICATE_SAME_ACCESS // djeca ne nasljeuju kod // kopira prava pristupa iz originalnog // identifikacionog koda );
Dok CloseHandle nema nikakav efekat na pseudoidentifikacioni kod, identifikacioni kod iz DuplicateHandle je realan i mora se eventualno zatvoriti. Korienje pseudoidentifikacionog koda omoguava da GetCurrentThread radi bre, jer pretpostavlja da bi nit trebala da ima pun pristup samoj sebi i vraa svoj rezultat, a da pri tom ne uzima u obzir siguronosne karakteristike.
strana 28
Ba kao to se i Windows program zavrava kada doe do kraja WinMain funkcije, nit se normalno zavrava kada doe do kraja funkcije gdje je poela. Kada nit doe do kraja svoje poetne funkcije, sistem automatski poziva funkciju ExitThread.
VOID WINAPI ExitThread( DWORD dwExitCode ); // izlazni kod za nit koja se zavrava
Iako sistem automatski poziva funkciju ExitThread, moemo je pozvati direktno ako neki uslov primorava nit da se izvrava neogranieno.
// Poslovi inicijalizacije se deavaju ovdje. // Testirajte da vidite da li je bilo problema. if ( <stanje greke> ) { ExitThread( ERROR_CODE ); } // otkazuje se nit
// nema greke, rad se nastavlja return( SUCCESS_CODE ); // ova linija prouzrokuje da sistem pozove ExitThread }
ERROR_CODE i SUCCESS_CODE su onakvi kakvim ih definiemo. U ovom primjeru mogli smo dovoljno lako da otkaemo nit komandom return:
strana 29
Komanda return ima isti efekat kao ExitThread; u stvari ona ak kao rezultat ima poziv ExitThread. Kada se nit zavri iskazom return, DWORD (unsigned long) povratna vrijednost postaje izlazni kod koji se automatski prosljeuje funkciji ExitThread. Kada se nit zavrila, njen izlazni kod je dostupan preko sljedee funkcije:
BOOL WINAPI GetExitCodeThread( HANDLE hThread, LPDWORD lpExitCode ); // nit za koju se trai izlazni kod // DWORD rije u kojoj e biti smjeten kod
Funkcija GetExitCodeThread vraa FALSE ako je greka sprjeava da utvrdi povratnu vrijednost. Funkcija ExitThread, bilo da se poziva eksplicitno ili implicitno kao posljedica iskaza return, permanentno uklanja nit iz reda i unitava stek niti. Ipak, ona ne unitava objekat nit, to omoguava upite o izlaznom statusu niti ak i kada nit prestane da se izvrava. Kada je mogue, identifikacioni kodovi bi trebalo da se zatvore eksplicitno (pozivom funkcije CloseHandle) da bi se izbjeglo zauzimanje prostora u memoriji. Sistem zavrava nit kada su zadovoljena sljedea dva uslova: Kada je posljednji identifikacioni kod niti zatvoren; Kada se nit vie ne izvrava. Sistem nee unititi nit koja se izvrava, ak iako su svi njeni identifikacioni kodovi zatvoreni. Umjesto toga, nit nije unitena sve dok ne prestane da se izvrava. Ipak, ako proces ostavi otvorene identifikacione kodove kada se zavrava, sistem ih automatski zatvara i uklanja objekte siroie koje vie ne dri ni jedan proces.
strana 30
Funkcijom ExitThread nit se graciozno zaustavlja na mjestu koje je sama izabrala, dok funkcija TerminateThread dozvoljava da jedna nit stopira drugu naglo i proizvoljno:
BOOL WINAPI TerminateThread( HANDLE hThread, // nit koju treba stopirati DWORD dwExitCode ); // izlazni kod za nit koja se stopira
Nit se ne moe zatititi od zavravanja. Svako ko ima identifikacioni kod niti, takav da je THREAD_TERMINATE zastavica setovana, moe primorati nit da se momentalno zaustavi, nezavisno od njenog trenutnog stanja. Korienje podrazumijevanih atributa sigurnosti u funkciji CreateThread proizvodi identifikacioni kod sa punim privilegijama pristupa. TerminateThread ne unitava stek niti ali obezbjeuje izlazni kod. I ExitThread i TerminateThread postavljaju objekat niti u njegovo signalizirano stanje, tako da bilo koje druge niti koje ekaju ovu da se zavri mogu da nastave rad. Poslije bilo koje od ovih komandi, objekat niti stoji beivotno dok se svi njegovi identifikacioni kodovi ne zatvore.
strana 31
4. Sinhronizacija niti
Da bi radili sa nitima, moramo biti u mogunosti da koordiniramo njihove akcije. Ponekad koordinacija podrazumijeva da treba osigurati da se odreene akcije deavaju u odreenom redosljedu. Pored funkcija za kreiranje niti i modifikaciju njihovog prioriteta, Win32 API sadri funkcije koje mogu primorati niti da ekaju signale od objekata, kao to su fajlovi ili procesi. Podrani su, takoe, specijalni sinhronizacioni objekti, kao to su mutexi i semafori. Funkcije koje ekaju da objekat dostigne svoje signalizirano stanje, najbolje ilustruju kako se koriste sinhronizacioni objekti. Sa jednim setom generikih komandi za ekanje moemo da ekamo procese, niti, mutexe, semafore, dogaaje i nekoliko drugih objekata da dostignu svoje signalizirano stanje. Sljedeom komandom ekamo da jedan objekat ukljui svoj signal:
DWORD WINAPI WaitForSingleObject( HANDLE hHandle, // objekat koji se eka DWORD dwMilliseconds // maksimalno vrijeme ekanja );
strana 32
Funkcija WaitForSingleObject dozvoljava niti da suspenduje samu sebe, dok odreeni objekat ne da svoj signal. U okviru ove komande nit takoe navodi koliko je dugo spremna da eka objekat. Da bi vrijeme ekanja bilo neogranieno dugo, potrebno je podesiti interval na INFINITE. Ako je objekat ve dostupan, ili ako dostigne svoje signalizirano stanje u okviru naznaenog vremena, funkcija WaitForSingleObject vraa nulu i izvravanje se nastavlja. Ako interval proe, a objekat jo nije signaliziran, funkcija vraa WAIT_TIMEOUT. Da bi nit ekala nekoliko objekata odjednom, koristimo WaitForMultipleObjects. Moemo podesiti da ova funkcija vraa rezultat im bilo koji od objekata postane dostupan, ili da eka dok svi objekti konano ne dostignu svoje signalizirano stanje. Program koji upravlja dogaajima mogao bi da podesi niz objekata koji ga interesuju i da reaguje kada neki od njih bude signaliziran.
DWORD WINAPI WaitForMultipleObjects( DWORD nCount, const HANDLE* lpHandles, BOOL bWaitAll, DWORD dwMilliseconds ); // broj objekata koje treba ekati // niz identifikacionih kodova objekata // maksimalni period ekanja // TRUE eka sve, FALSE eka bilo koji
Ponovo, povratna vrijednost WAIT_TIMEOUT ukazuje na to da je interval proao i da ni jedan objekat nije signalizirao. Ako je bWaitAll setovan na FALSE uspjena povratna vrijednost koja nosi indikator bilo kojeg elementa, ukazuje na to koji je element u nizu lpHandles bio signaliziran (indeksi su od 0 (nula) do nCount - 1). Ako je bWaitAll setovan na TRUE, funkcija ne vraa kontrolu dok svi indikatori (sve niti) ne budu kompletirani. Dvije proirene verzije funkcija ekanja dodaju status uzbune koji dozvoljava niti da se rezimira ako se asinhrona komanda itanja ili upisa zavri tokom ekanja. Efekat ovih funkcija je takav da one kau: Probudite me ako objekat postane dostupan, ako proe odreeno vrijeme ili ako se I/O operacija u pozadini blii kraju.
strana 33
DWORD WINAPI WaitForMultipleObjectsEx( DWORD nCount, const HANDLE* lpHandles, BOOL bWaitAll, // broj objekata koji se eka // niz identifikacionih kodova objekata // TRUE eka sve, FALSE eka bilo koji
Uspjene komande ekanja obino na neki nain modifikuju objekat koji se eka. Na primjer, kada nit eka mutex i kada ga dobije, funkcija ekanja vraa mutex u njegovo nesignalizirano stanje da bi druge niti znale da se on koristi. Komande ekanja takoe smanjuju vrijednost brojaa u semaforu i resetuju neke vrste dogaaja. Komande ekanja ne modifikuju stanje navedenog objekta dok svi objekti ne budu simultano signalizirani. Na primjer, mutex moe biti signaliziran, ali nit ne prima vlasnitvo odmah zato to se od nje zahtijeva da eka dok ostali objekti ne budu takoe signalizirani; zato funkcija ekanja ne moe da modifikuje objekat. Pored toga, mutex moe da postane vlasnitvo druge niti dok eka, to e jo vie odloiti zavretak stanja ekanja.
Funkcijama za kreiranje mutexa i semafora treba da specifikujemo koje privilegije pristupa elimo, neke prvobitne uslove za objekat i opciono ime za objekat.
strana 34
HANDLE WINAPI CreateSemaphore( LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, // opcioni atributi sigurnosti LONG lInitialCount, LONG lMaximumCount, LPCTSTR lpName ); // inicijalni broj (obino 0) // maksimalni broj niti // ime semafora
Ako je deskriptor sigurnosti NULL, vraeni identifikacioni kod e posjedovati sve privilegije pristupa i nee ga naslijediti procesi djeca. Imena su opciona, ali korisna za identifikaciju samo kada nekoliko razliitih procesa eli identifikacione kodove za isti objekat. Postavljanjem indikatora bInitialOwner na TRUE, nit kreira i dobija mutex istovremeno. Novi mutex ostaje nesignaliziran dok ga nit ne oslobodi. Dok samo jedna nit u jednom trenutku moe dobiti mutex, semafor ostaje signaliziran dok njegov akvizicioni broj ne dostigne lMaximumCount. Ako jo neke niti pokuaju da ekaju semafor, bie suspendovane dok neka druga nit ne smanji akvizicioni broj.
Jednom kada je stvoren semafor ili mutex, niti vre interakciju sa njim tako to ga preuzimaju ili oslobaaju. Da bi preuzela bilo koji od navedenih objekata, nit poziva funkciju WaitForSingleObject (ili neku njenu varijantu). Kada nit zavri bilo koji zadatak koji objekat sinhronizuje, ona oslobaa objekat pomou jedne od sljedeih funkcija:
strana 35
BOOL WINAPI ReleaseSemaphore( HANDLE hSemaphore, LONG lReleaseCount, LPLONG lpPreviousCount ); // semafor koji se oslobaa // veliina inkrementa pri oslobaanju (obino 1) // promjenljiva koja prihvata prethodni broj
Oslobaanje semafora ili mutexa inkrementira njegov broja. Kad god vrijednost brojaa pree nulu, objekat prelazi u svoje signalizirano stanje, a sistem provjerava da li ga ekaju neke druge niti. Samo nit koja ve posjeduje mutex, drugim rijeima, nit koja je ve ekala mutex, moe ga osloboditi. Bilo koja nit, ipak, moe pozvati ReleaseSemaphore da bi podesila akvizicioni broja na bilo koju vrijednost, najvie do svoje maksimalne vrijednosti. Promjena vrijednosti brojaa za proizvoljne vrijednosti omoguava nam da mijenjamo broj niti koje mogu posjedovati semafor dok se program izvrava. Funkcija CreateSemaphore omoguava da podesimo broja za novi semafor na neku drugu vrijednost osim maksimalne. Mogli bi, na primjer, da ga kreiramo sa prvobitnom vrijednou, nulom, kako bi blokirali sve niti dok se program inicijalizuje, a zatim da poveamo vrijednost brojaa pomou ReleaseSemaphore. Nit moe ekati isti objekat vie od jednom bez blokiranja, ali svako ekanje mora biti propraeno kasnijim oslobaanjem. Ovo vai za mutexe, semafore i kritine sekcije.
Rad sa dogaajima
strana 36
Dogaaj je objekat koji program kreira kada mu je potreban mehanizam za uzbunjivanje niti kada se desi neka akcija. U svojoj najprostijoj formi dogaaj runog resetovanja objekat dogaaj ukljuuje i iskljuuje svoj signal kao odgovor na dvije komande SetEvent (signal ukljuen) i ResetEvent (signal iskljuen). Kada je signal ukljuen , sve niti koje ekaju dogaaj primie ga. Kada je signal iskljuen, sve niti koje ekaju dogaaj postaju blokirane. Za razliku od mutexa i semafora, dogaaji runog resetovanja mijenjaju svoje stanje samo kada ih neka nit eksplicitno setuje ili resetuje. Dogaaj runog resetovanja moemo koristiti da dozvolimo da se odreene niti izvravaju samo kada program ne boji svoj prozor ili samo poto korisnik unese odreene informacije. Slijede osnovne komande za rad sa dogaajima:
HANDLE WINAPI CreateEvent( LPSECURITY_ATTRIBUTES lpEventAttributes, BOOL bManualReset, BOOL bInitialState, // privilegije sigurnosti // TRUE ako dogaaj mora biti resetovan runo // TRUE da se kreira dogaaj u // signaliziranom stanju LPCTSTR lpName ); // ime dogaaja
Korienjem parametra bInitialState, CreateEvent omoguava da se novi dogaaj (objekat) kreira ve signaliziran. Funkcije SetEvent i ResetEvent vraaju TRUE ili FALSE da ukau na uspjeh ili neuspjeh. Korienjem parametra bManualReset, CreateEvent dozvoljava da kreiramo automatski dogaaj resetovanja umjesto runog dogaaja resetovanja. Automatski dogaaj resetovanja vraa se u svoje nesignalizirano stanje odmah poslije komande SetEvent i zato je komanda ResetEvent suvina.
strana 37
Puls ukljuuje signal za veoma kratko vrijeme i ponovo ga iskljuuje. Pulsiranje runog dogaaja omoguava svim nitima koje ekaju da prou, i onda resetuje dogaaj. Pulsiranje automatskog dogaaja dozvoljava jednoj niti koja eka da proe, i onda resetuje dogaaj. Ako ni jedna nit ne eka, nijedna nee ni proi. Setovanje automatskog dogaaja, sa druge strane, prouzrokovae da dogaaj ostavi svoj signal ukljuen dok ga neka nit eka. im jedna nit proe, dogaaj se sam resetuje.
Procesi, ak i nevezani procesi, mogu dijeliti mutexe, semafore i dogaaje. Diobom objekata procesi mogu da koordiniraju svoje aktivnosti, kao to to mogu niti. Postoje tri mehanizma za diobu. Jedan je nasljeivanje, kada jedan proces kreira drugi, i novi proces dobije kopije identifikacionih kodova roditelja. Samo ti identifikacioni kodovi, koji su markirani za nasljeivanje, kada su kreirani, bie proslijeeni.
strana 38
Drugi metodi ukljuuju pozivanje funkcija za kreiranje drugog identifikacionog koda za postojei objekat. Koju funkciju pozivamo zavisi od toga koje informacije ve imamo. Ako imamo identifikacione kodove i za izvorni i za odredini proces, koristimo funkciju DuplicateHandle. Ako imamo ime objekta koristimo neku od Open funkcija. Dva programa mogu se sloiti unaprijed kada su u pitanju imena objekata koje dijele, ili jedan moe proslijediti drugom ime objekta preko zajednike memorije ili kanala.
BOOL WINAPI DuplicateHandle( HANDLE hSourceProcessHandle, HANDLE hSourceHandle, HANDLE hTargetProcessHandle, // proces koji posjeduje originalni objekat // identifikacioni kod za originalni objekat // proces koji eli kopiju // identifikacionog koda LPHANDLE lpTargetHandle, // promjenljiva za smjetanje duplikata // identifikacionog koda DWORD dwDesiredAccess, BOOL bInheritHandle, // zahtijevane privilegije pristupa // da li duplikat identifikacionog koda // moe biti naslijeen DWORD dwOptions ); // opcione akcije (zastavice)
HANDLE WINAPI OpenMutex( DWORD dwDesiredAccess, BOOL bInheritHandle, // zahtijevane privilegije pristupa // TRUE ako djeca mogu da naslijede // ovaj identifikacioni kod LPCTSTR lpName ); // ime mutexa
HANDLE WINAPI OpenSemaphore( DWORD dwDesiredAccess, BOOL bInheritHandle, // zahtijevane privilegije pristupa // TRUE ako djeca mogu da naslijede // ovaj identifikacioni kod LPCTSTR lpName ); // ime semafora
HANDLE WINAPI OpenEvent( DWORD dwDesiredAccess, BOOL bInheritHandle, // zahtijevane privilegije pristupa // TRUE ako djeca mogu da naslijede // ovaj identifikacioni kod
strana 39
LPCTSTR lpName );
Mutexi, semafori i dogaaji ostaju u memoriji dok se svi procesi koji ih posjeduju ne zavre, ili dok se svi identifikacioni kodovi objekata ne zatvore pomou CloseHandle.
BOOL WINAPI CloseHandle( HANDLE hObject ); // identifikcioni kod objekta koji treba zatvoriti
Objekat kritina sekcija obavlja u potpunosti istu funkciju kao mutex, osim to se kritina sekcija ne moe dijeliti. Ona je vidljiva samo u okviru jednog procesa. I kritine sekcije i mutexi dozvoljavaju da ih posjeduje samo jedna nit u jednom trenutku, ali kritine sekcije rade mnogo bre i imaju manje zaglavlje. Funkcije za rad sa kritinim sekcijama ne koriste istu terminologiju, kao funkcije za rad sa mutexima, ali rade uglavnom iste stvari. Prvo se deklarie objekat tipa kritina sekcija:
CRITICAL_SECTION cs;
typedef struct _RTL_CRITICAL_SECTION { PRTL_CRITICAL_SECTION_DEBUG DebugInfo; LONG LockCount; LONG RecursionCount; HANDLE OwningThread;
strana 40
} RTL_CRITICAL_SECTION, *PRTL_CRITICAL_SECTION;
Tip promjenljive LPCRITICAL_SECTION imenuje pokaziva (ne identifikacioni kod) na objekat kritina sekcija.
Za razliku od ostalih objekata koji su ekali neku od wait funkcija, objekat kritina sekcija radi na drugaiji nain. Pomou funkcije:
objekat kritina sekcija ulazi u svoje nesignalizirano stanje. Poto zavri posao, pozivom funkcije:
strana 41
Kad nit zavri rad sa objektom kritina sekcija, potrebno je da ga uniti i to radi
strana 42
Programom prikazanim na slici 5 obuhvaeno je nekoliko ideja koje su obraivane u ovom radu. Program odreuje broj procesora (logikih jezgara) u vrijeme izvravanja i kreira isto toliki broj sekundarnih niti, a svaka od njih nasumice crta obojene pravougaonike nasumine veliine u prozoru djetetu dok se program ne zavri.
Procedure za inicijalizaciju
Procedure za inicijalizaciju registruju dvije klase prozora: jednu za glavni, preklapajui, prozor i jednu za prozore djecu gdje niti crtaju; a takoe kreiraju i tajmer. U intervalima od 250 milisekundi, polje sa listom aurira informacije o svakoj niti. Funkcija CreateWindows kreira i pozicionira sve prozore, ukljuujui i polje sa listom koja prikazuje informacije o svakoj niti. Pored registrovanja klase prozora (primarne niti) aplikacije i praenja uobiajne procedure za instalaciju, procedura InitInstance podeava prioritet niti i startuje sve sekundarne niti u suspendovanom stanju.
BOOL InitInstance( HINSTANCE hInstance, int nCmdShow ) { // ... // oznaimo prvobitno stanje niti kao SUSPENDED for ( int i = 0; i < THREAD_COUNT; i++ ) { iState[ i ] = SUSPENDED; } // ... // podesimo da primarna nit bude vanija da bi se olakao ulaz/izlaz SetThreadPriority( GetCurrentThread( ), THREAD_PRIORITY_ABOVE_NORMAL ); // ... return CreateWindows( hwndMain ); }
strana 44
BOOL CreateWindows( HWND hwndParent ) { // ... // kreiramo list view hwndListView = CreateWindowEx( WS_EX_OVERLAPPEDWINDOW, WC_LISTVIEW, NULL, WS_BORDER | WS_CHILD | WS_VISIBLE | LVS_SINGLESEL | LVS_REPORT | LVS_SHOWSELALWAYS, 0, 0, 0, 0, hwndParent, ( HMENU ) ID_LISTVIEW, gInstance, NULL ); if( ! hwndListView ) { return( FALSE ); } // ... WNDCLASSEX wcex; wcex.cbSize wcex.style wcex.lpfnWndProc wcex.cbClsExtra wcex.cbWndExtra wcex.hInstance wcex.hIcon wcex.hCursor wcex.hbrBackground wcex.lpszMenuName wcex.lpszClassName wcex.hIconSm = = = = = = = = = = = = sizeof( WNDCLASSEX ); CS_HREDRAW | CS_VREDRAW; ThreadWndProc; 0; 0; gInstance; LoadIcon( gInstance, MAKEINTRESOURCE(IDI_NITI) ); LoadCursor( NULL, IDC_ARROW ); ( HBRUSH ) ( COLOR_WINDOW + 1 ); MAKEINTRESOURCE( IDC_NITI ); lpThreadClassName; LoadIcon( gInstance, MAKEINTRESOURCE(IDI_NITI) );
if ( ! RegisterClassEx( &wcex ) ) { return FALSE; } // ... // kreiramo 4 prozora for ( iCount = 0; iCount < THREAD_COUNT; iCount++ ) { hwndChild[ iCount ] = CreateWindow( lpThreadClassName, NULL, WS_BORDER | WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN, 0, 0, 0, 0, hwndParent, NULL, gInstance, NULL ); if ( ! hwndChild[ iCount ] ) { return( FALSE ); } }
strana 45
Main_OnTimer poziva proceduru UpdateListView da raisti polje sa listom, generie nove stringove za informacije i vri prikaz istih. Funkcija Main_OnSize suspenduje sve sekundarne niti dok program mijenja poziciju prozora djece da bi ta pozicija odgovarala novoj veliini. U suprotnom, zauzete niti usporavale bi operaciju prikaza. Pored kreiranja niti OnCreate kreira mutex i kompletira proces inicijalizacije niti i proces podeavanja sinhronizacije mutexa.
BOOL OnCreate( HWND hWnd ) { UINT uRet; PROCESSOR_NUMBER previousProcessorNumber = { 0 }; PROCESSOR_NUMBER processorNumber = { 0 }; // kreiramo 4 niti , inicijalno suspendovane for ( iCount = 0; iCount < THREAD_COUNT; iCount++ ) { iRectCount[ iCount ] = 0; dwThreadData[ iCount ] = iCount; hThread[ iCount ] = CreateThread( NULL, 0, (LPTHREAD_START_ROUTINE) StartThread, ( LPVOID ) ( &( dwThreadData[ iCount ] ) ), CREATE_SUSPENDED, ( LPDWORD ) ( & ( dwThreadID[ iCount ] ) ) ); processorNumber.Number = iCount; SetThreadIdealProcessorEx( hThread[ iCount ], &processorNumber, &previousProcessorNumber ); if( ! hThread[ iCount ] ) { // ako nit nije kreirana greska - izadji return( FALSE ); } } // Kreiramo tajmer sa 5 sekundi periodom. // Tajmer koristimo za update list view-a. uRet = ( UINT ) SetTimer( hWnd, TIMER, 100, NULL );
strana 46
// kreiramo sinhronizacioni objekat mutex hDrawMutex = CreateMutex( NULL, FALSE, NULL ); if ( ! hDrawMutex ) { // nije bilo moguce kreirati mutex KillTimer( hWnd, TIMER ); // zaustavi tajmer return( FALSE ); } // startujemo niti sa prioritetom below normal for ( iCount = 0; iCount < THREAD_COUNT; iCount++ ) { SetThreadPriority( hThread[ iCount ],THREAD_PRIORITY_BELOW_NORMAL); iState[ iCount ] = ACTIVE; ResumeThread( hThread[ iCount ] ); } RECT rect; GetClientRect( hWnd, &rect ); Main_OnSize( hWnd, 0, rect.right, rect.bottom ); return( TRUE ); } void Main_OnTimer( HWND hWnd, UINT uTimerID ) { UpdateListView( ); // update list view return; } void Main_OnSize( HWND hWnd, UINT uState, int cxClient, int cyClient { // suspendujemo sve aktivne niti da bi se obavile // izmjene u poziciji i velicini prozora for ( iCount = 0; iCount < THREAD_COUNT; iCount++ ) { if ( iState[ iCount ] == ACTIVE ) { SuspendThread( hThread[ iCount ] ); iState[ iCount ] = SUSPENDED; } } // postavimo list view na odradjenoj poziciji MoveWindow( hwndListView, 0, 0, cxClient, LISTVIEW_HEIGHT, TRUE ); MoveWindow( hwndChild[ 0 ], 0, LISTVIEW_HEIGHT - 1, cxClient / THREAD_COUNT + 1, cyClient, TRUE ); for ( iCount = 1; iCount < THREAD_COUNT; iCount++ ) { MoveWindow( hwndChild[ iCount ], (iCount*cxClient) / THREAD_COUNT, LISTVIEW_HEIGHT-1, cxClient/THREAD_COUNT + 1, cyClient, TRUE ); } for ( iCount = 0; iCount < THREAD_COUNT; iCount++ ) { ) // sada se sve niti izvrsavaju!
strana 47
iRectCount[ iCount ] = 0; }
// rezimiramo niti for ( iCount = 0; iCount < THREAD_COUNT; iCount++ ) { if ( iState[ iCount ] == SUSPENDED ) { ResumeThread( hThread[ iCount ] ); iState[ iCount ] = ACTIVE; } } return; }
Procedure za modifikaciju
Procedura DoThread odgovara na komande menija tako to modifikuje nit odabranu iz liste. DoThread moe promijeniti prioritet niti, suspendovati je ili rezimirati. Niz iState snima trenutno stanje svake niti (aktivno ili suspendovano). Niz hThreads sadri identifikacione kodove svake sekundarne niti.
void DoThread( int iCmd ) { int iThread; int iPriority; // sacuvaj indeks niti iz list view-a iThread = ListView_GetNextItem(hwndListView, -1, LVNI_SELECTED ); switch( iCmd ) { case IDM_SUSPEND: { if( iState[ iThread ] != SUSPENDED ) { SuspendThread( hThread[ iThread ] ); iState[ iThread ] = SUSPENDED; } break; } case IDM_RESUME: { if( iState[ iThread ] != ACTIVE ) { ResumeThread( hThread[ iThread ] ); iState[ iThread ] = ACTIVE;
strana 48
} break;
} case IDM_INCREASE: { iPriority = GetThreadPriority( hThread[ iThread ] ); switch( iPriority ) { case THREAD_PRIORITY_LOWEST: { SetThreadPriority( hThread[ iThread ], THREAD_PRIORITY_BELOW_NORMAL ); break; } case THREAD_PRIORITY_BELOW_NORMAL: { SetThreadPriority( hThread[ iThread ], THREAD_PRIORITY_NORMAL ); break; } case THREAD_PRIORITY_NORMAL: { SetThreadPriority( hThread[ iThread ], THREAD_PRIORITY_ABOVE_NORMAL ); break; } case THREAD_PRIORITY_ABOVE_NORMAL: { SetThreadPriority( hThread[ iThread ], THREAD_PRIORITY_HIGHEST ); break; } default: { break; } } break; } case IDM_DECREASE: { iPriority = GetThreadPriority( hThread[iThread] ); switch( iPriority ) { case THREAD_PRIORITY_BELOW_NORMAL: { SetThreadPriority( hThread[ iThread ], THREAD_PRIORITY_LOWEST ); break; } case THREAD_PRIORITY_NORMAL: { SetThreadPriority( hThread[iThread], THREAD_PRIORITY_BELOW_NORMAL ); break; } case THREAD_PRIORITY_ABOVE_NORMAL: { SetThreadPriority( hThread[iThread], THREAD_PRIORITY_NORMAL ); break; }
strana 49
case THREAD_PRIORITY_HIGHEST: { SetThreadPriority( hThread[iThread], THREAD_PRIORITY_ABOVE_NORMAL ); break; } default: { break; } } break; } default: { break; } } return; }
Procedure za niti
Kada funkcija OnCreate konstruie sekundarne niti, prosljeuje pokaziva na funkciju StartThread pri svakom pozivu funkcije CreateThread. StartThread postaje glavna procedura za sve sekundarne niti i taka gdje one poinju i zavravaju se. Ako je bUseMutex TRUE, niti e ekati da dobiju mutex prije nego to ponu da crtaju, a samo jedna nit e crtati u jednom trenutku.
DWORD WINAPI StartThread( LPVOID lpThreadData ) { DWORD dwWait; // povratna vrijednost za WaitSingleObject // sacuvamo ID niti LPDWORD pdwThreadID = ( LPDWORD ) lpThreadData; // niti crtaju sve dok je bTerminate razlicito od TRUE while ( ! bTerminate ) { if ( bUseMutex ) // da li koristimo mutex? { // nit ceka mutex, a zatim crta dwWait = WaitForSingleObject( hDrawMutex, INFINITE ); if( dwWait == 0 ) { DrawProc( *pdwThreadID ); // crtamo pravoguaonike Sleep( GetSleepTime( GetThreadPriority( hThread[ *pdwThreadID ] ) ) );
strana 50
ReleaseMutex( hDrawMutex ); // nit oslobadja mutex } } else // ne koristimo mutex pocni crtanje { DrawProc( *pdwThreadID ); Sleep( GetSleepTime( GetThreadPriority( hThread[ *pdwThreadID ] ) ) ); } } // ova naredba implicitno poziva ExitThread. return 0L; }
Nakon poziva funkcije DrawProc svakoj niti se odreuje vrijeme spavanja u odnosu na prioritet iste. Ovo je bitno iz razloga to bi niti inae veoma brzo zavravale svoj posao crtanje (red veliine je manje od milisekunde), pa gledajui izvravanje programa ne bi vidjeli kako se posao stvarno odvija. Zato funkcija GetSleepTime rauna vrijeme spavanja u odnosu na prioritet niti.
int GetSleepTime( int iPriority ) { switch ( iPriority ) { case THREAD_PRIORITY_LOWEST: { return 2000; } case THREAD_PRIORITY_BELOW_NORMAL: { return 1000; } case THREAD_PRIORITY_NORMAL: { return 500; } case THREAD_PRIORITY_ABOVE_NORMAL: { return 250; } case THREAD_PRIORITY_HIGHEST: { return 0; } } return INFINITE; }
strana 51
Zakljuak
Sinhronizacija niti nije ni malo jednostavan zadatak, posebno kada su u pitanju veliki sistemi gdje itave cjeline moraju koordinirano dijeliti resurse. A uz to i same cjeline unutar sebe moraju sinhronizovati zadatke (niti) tako da ne smetaju jedna drugoj. U ovom radu obraeni su samo osnovni koncepti navedene tematike koliko da se predstavi zadatak sinhronizacije i pokau mehanizmi kojima se sinhronizacija sprovodi.
strana 52
Literatura
1. Modern Operating Systems 3rd Edition, Andrew S. Tanenbaum 2. Operating System Concepts, Abraham Silberschatz 3. Mastering Windows 2000 Programming in C++, Ben Ezzell 4. MSDN, Microsoft Developer Network
strana 53