You are on page 1of 53

UNIVERZITET CRNE GORE Prirodno matematiki fakultet Podgorica

Milo Ljumovi Koncept niti u savremenim Windows operativnim sistemima Kreiranje i sinhronizacija niti
ZAVRNI RAD

Podgorica, 2011

UNIVERZITET CRNE GORE Prirodno matematiki fakultet Podgorica

Kreiranje i sinhronizacija niti

Koncept niti u savremenim Windows operativnim sistemima Kreiranje i sinhronizacija niti


ZAVRNI RAD

Operativni sistemi Predrag Stanii Milo Ljumovi Raunarstvo i informacione tehnologije 2607980210013

Podgorica, februar, 2012


strana 2

Kreiranje i sinhronizacija niti

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

Kreiranje i sinhronizacija niti

Sadraj

Saetak Uvod Istorijat

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

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

5. Primjer sa viestrukim nitima - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ---------------------------------------------------------------------------------------------------------

strana 4

Kreiranje i sinhronizacija niti

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

Kreiranje i sinhronizacija niti


Vrlo vaan koncept u svim operativnim sistemima je nit, ili nit kontrole. Objekat nit posjeduje programski broja koji vodi evidenciju (rauna) koja instrukcija treba da se izvri naredni put kada nit dobije svoje vrijeme na procesoru, registre koji uvaju tekue vrijednosti promjenljivih s kojima nit manipulie, stek gdje se smjetaju podaci vezani za pozive funkcija (procedura) koje jo nisu vratile kontrolu pozivau, itd. Iako se nit izvrava u kontekstu procesa, niti i procesi su jako razliiti koncepti i treba ih posmatrati odvojeno. Procesi se koriste (posmatrano uopte) da objedine zajednike resurse; niti su entiteti (najmanja jedinica) koji se zakazuju za izvravanje na procesoru. U ovom radu konceptualno emo predstaviti niti, zatim dati pregled komandi za korienje niti i zavriti programom koji demonstrira kreiranje, sinhronizaciju i korienje viestrukih niti.

strana 6

Kreiranje i sinhronizacija niti

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

Kreiranje i sinhronizacija niti

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

Kreiranje i sinhronizacija niti

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

Kreiranje i sinhronizacija niti


Razliite niti u procesima nisu toliko nezavisne, koliko su razliiti procesi. Sve niti imaju isti adresni prostor, to znai da dijele sve globalne promjenljive. Obzirom da sve niti jednog procesa mogu koristiti sav adresni prostor procesa, jedna nit moe itati, pisati ili ak potpuno izbrisati podatke vezane za drugu nit. Ne postoji zatita na nivou niti koja bi ih sprijeila da jedna drugoj kvare podatke, prvo zato to je to nemogue implementirati, a drugo zato to za tim nema potrebe. Nasuprot procesima, koji se utrkuju za resurse i neprijateljski su nastrojeni jedan prema drugom, niti pripadaju jednom procesu, dijele njegove resurse i prijateljski su nastrojene. Da bi se sprijeila meusobna interferenca niti uveemo mehanizme za sinhronizaciju niti, koji e nam omoguiti siguran rad sa viestrukim nitima. Interesanta primjena niti je kod grafikog korisnikog interfejsa (GUI), kod takvog sistema aplikacije uglavnom posjeduju prozor (formu), koji je ujedno komunikacija (interface) sa korisnikom. Svaki prozor koji kreira program pripada niti koja ga je kreirala. Kada nit kreira prozor, sistem joj dodjeljuje red poruka, a nit mora da ue u petlju poruka da bi itala iz svog reda. Svaka nit koja eli da prima poruke, mora da kreira prozor za sebe, ak i ako prozor ostane sakriven. Samo niti koje kreiraju prozore dobijaju redove poruka.

Kada treba kreirati niti i procese

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.

Kreiranje i sinhronizacija niti

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

Kreiranje i sinhronizacija niti

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.

Objekti i identifikacioni kodovi

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.

Kreiranje i sinhronizacija niti

strana 13

Kreiranje i sinhronizacija niti

2. Raspored i sinhronizacija niti

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.

Prioritet procesa, osnovni prioritet i dinamiki prioritet

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

Kreiranje i sinhronizacija niti


Kao to je ranije prikazano na slici 3, atributi objekta nit ukljuuju osnovni prioritet i dinamiki prioritet. Kada pozivamo komande za promjenu prioriteta niti, mijenjamo samo osnovni prioritet. Ipak, nije mogue promijeniti prioritet niti za vie od dva koraka gore ili dolje u odnosu na prioritet njenog procesa. Drugim rijeima, nit ne moe da bude mnogo bitnija od svog roditelja. Iako proces ne moe da podigne svoje niti na visok prioritet, sistem moe. Sistem dozvoljava neku vrstu unapreenja polja dinamiki prioritet nitima koje preuzimaju vane zadatke. Kada korisnik izvri neki unos u prozor, sistem uvijek podie na vii nivo sve niti u procesu koji posjeduje prozor. Kada nit koja eka podatke sa vrstog diska te podatke konano i primi, sistem unapreuje i tu nit. Ova privremena unapreenja, kada se dodaju na osnovni prioritet, formiraju dinamiki prioritet. Dispeer (Dispatcher), odnosno element za pravljenje rasporeda, bira niti za izvravanje na osnovu njihovog dinamikog prioriteta. Osnovni i dinamikig prioritet procesa dat je na slici 4.

strana 15

Kreiranje i sinhronizacija niti

Slika 4. Kako se opseg prioriteta niti izvodi iz prioriteta procesa

strana 16

Kreiranje i sinhronizacija niti


Poveanja dinamikog prioriteta poinju da opadaju momentalno, nakon zavrenog zadatka. Dinamiki prioritet niti sputa se za jedan stepen unazad svaki put kada nit primi sljedei dio vremena na CPU-om i konano se stabilizuje na osnovnom proritetu niti.

Kako se pravi raspored

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.

Kreiranje i sinhronizacija niti

Kako se odvija sinhronizacija

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

Kreiranje i sinhronizacija niti

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.

Kreiranje i sinhronizacija niti

strana 20

Kreiranje i sinhronizacija niti

3. Komande vezane za niti

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.

Kreiranje i modifikacija 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

Kreiranje i sinhronizacija niti

LPTHREAD_START_ROUTINE lpStartAddress, // pokaziva na funkciju LPVOID lpvThreadParam, DWORD fdwCreate, LPDWORD lpThreadID );

// vrijednost proslijeena funkciji // aktivan ili suspendovan // sistem ovdje vraa ID

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

typedef struct _SECURITY_ATTRIBUTES { DWORD nLength;

Kreiranje i sinhronizacija niti


// veliina strukture _SECURITY_ATTRIBUTES // NULL za prihvatanje deskriptora procesa // TRUE ako djeca mogu da naslijede objekat

LPVOID lpSecurityDescriptor; BOOL bInheritHandle;

} 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.

Kreiranje i sinhronizacija niti

Promjena prioriteta niti

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

);

Kreiranje i sinhronizacija niti

int WINAPI GetThreadPriority( HANDLE hThread ); // nit koju treba modifikovati

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:

THREAD_PRIORITY_LOWEST THREAD_PRIORITY_BELOW_NORMAL THREAD_PRIORITY_NORMAL THREAD_PRIORITY_ABOVE_NORMAL THREAD_PRIORITY_HIGHEST

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.

Suspendovanje i rezimiranje izvravanja niti

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:

Kreiranje i sinhronizacija niti

DWORD WINAPI SuspendThread( HANDLE hThread ); // nit koju treba suspendovati

DWORD WINAPI ResumeThread( HANDLE hThread ); // nit koju treba rezimirati

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.

VOID WINAPI Sleep( DWORD dwMilliseconds // trajanje pauze );

strana 26

Kreiranje i sinhronizacija niti


DWORD WINAPI SleepEx( DWORD dwMilliseconds, BOOL bAlertable ); // trajanje pauze // TRUE da se rezimira ako se I/O operacija zavri

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.

Dobijanje informacija o postojeim nitima

Nit moe lako dobiti dva dokaza o svom identitetu: identifikacioni kod i identifikator. Sljedee funkcije vraaju informacije koje identifikuju tekuu nit:

DWORD WINAPI GetCurrentThreadId( void );

HANDLE WINAPI GetCurrentThread( void );

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 );

Kreiranje i sinhronizacija niti

HANDLE hThread; DuplicateHandle( GetCurrentProcess( ), GetCurrentThread( ), GetCurrentProcess( ), &hThread, 0,

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

Kreiranje i sinhronizacija niti

Prekid izvravanja niti

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.

DWORD WINAPI ThreadFunction( LPVOID lpParameter ) { HANDLE hThread = CreateThread( <parametri> );

// 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

if ( <error condition> ) { return( ERROR_CODE ); }

Kreiranje i sinhronizacija niti


// otkazuje se nit

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:

Kreiranje i sinhronizacija niti

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

Kreiranje i sinhronizacija niti

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.

Kreiranje i sinhronizacija niti

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

Kreiranje i sinhronizacija niti


DWORD WINAPI WaitForSingleObjectEx( HANDLE hHandle, DWORD dwMilliseconds, // maksimalno vrijeme ekanja BOOL bAlertable ); // TRUE da se zavri ekanje ako se I/O zavri // objekat koji se eka

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

DWORD dwMilliseconds, // maksimalno vrijeme ekanja BOOL bAlertable );

// TRUE da se zavri ekanje ako se I/O zavri

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.

Kreiranje mutexa i semafora

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

Kreiranje i sinhronizacija niti


HANDLE WINAPI CreateMutex( LPSECURITY_ATTRIBUTES lpMutexAttributes, BOOL bInitialOwner, LPCTSTR lpName ); // opcioni atributi sigurnosti

// TRUE ako kreator eli vlasnitvo // ime objekta

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.

Preuzimanje i oslobaanje mutexa i semafora

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

Kreiranje i sinhronizacija niti


BOOL WINAPI ReleaseMutex( HANDLE hMutex ); // mutex koji se oslobaa

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:

Kreiranje i sinhronizacija niti

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

BOOL WINAPI SetEvent( HANDLE hEvent ); // dogaaj koji treba setovati

BOOL WINAPI ResetEvent( HANDLE hEvent ); // dogaaj koji treba resetovati

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

Kreiranje i sinhronizacija niti


Automatsko resetovanje uvijek oslobaa samo jednu nit na svaki signal prije resetovanja. Automatski dogaaj resetovanja bi mogao biti koristan za program gdje jedna glavna nit priprema podatke za druge, radne, niti. Svaki put kada je novi niz podataka spreman, glavna nit setuje dogaaj i jedna radna nit se oslobaa. Druge radne niti nastavljaju da ekaju u redu da bi dobile nove zadatke. Pored setovanja i resetovanja, dogaaj moemo i da pulsiramo:

BOOL WINAPI PulseEvent( HANDLE hEvent ); // dogaaj koji treba pulsirati

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.

Dioba i unitavanje mutexa, semafora i dogaaja

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.

Kreiranje i sinhronizacija niti

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 );

Kreiranje i sinhronizacija niti


// ime dogaaja

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

Rad sa kritinim sekcijama

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;

Objekat CRITICAL_SECTION definisan je na sljedei nain:

typedef struct _RTL_CRITICAL_SECTION { PRTL_CRITICAL_SECTION_DEBUG DebugInfo; LONG LockCount; LONG RecursionCount; HANDLE OwningThread;

strana 40

HANDLE LockSemaphore; ULONG_PTR SpinCount;

Kreiranje i sinhronizacija niti

} RTL_CRITICAL_SECTION, *PRTL_CRITICAL_SECTION;

typedef RTL_CRITICAL_SECTION CRITICAL_SECTION, *LPCRITICAL_SECTION;

Prije upotrebe objekta kritina sekcija, potrebno ga je inicijalizovati pomou funkcije:

void WINAPI InitializeCriticalSection( LPCRITICAL_SECTION lpCriticalSection );

Tip promjenljive LPCRITICAL_SECTION imenuje pokaziva (ne identifikacioni kod) na objekat kritina sekcija.

void WINAPI DeleteCriticalSection( LPCRITICAL_SECTION lpCriticalSection );

Za razliku od ostalih objekata koji su ekali neku od wait funkcija, objekat kritina sekcija radi na drugaiji nain. Pomou funkcije:

void WINAPI EnterCriticalSection( LPCRITICAL_SECTION lpCriticalSection );

objekat kritina sekcija ulazi u svoje nesignalizirano stanje. Poto zavri posao, pozivom funkcije:

void WINAPI LeaveCriticalSection( LPCRITICAL_SECTION lpCriticalSection );

strana 41

objekat kritina sekcija prelazi u svoje signalizirano stanje. pozivom funkcije:

Kreiranje i sinhronizacija niti

Kad nit zavri rad sa objektom kritina sekcija, potrebno je da ga uniti i to radi

void WINAPI DeleteCriticalSection( LPCRITICAL_SECTION lpCriticalSection );

strana 42

Kreiranje i sinhronizacija niti

5. Primjer sa viestrukim nitima

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.

Slika 5. Program Niti


strana 43

Kreiranje i sinhronizacija niti


Vrh prozora sadri polje za listu koja prikazuje informacije o svim nitima. Odabirom niti i biranjem komande menija moemo suspendovati, rezimirati i promijeniti prioritet odabrane niti. Odabirom komande Control moemo aktivirati Mutex, tako da samo jedna nit crta u jednom trenutku.

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

Kreiranje i sinhronizacija niti


Poziv SetThreadPriority poveava prioritet primarne niti. Ako su sve sekundarne niti bile zauzete, radei istim proritetom kao glavna nit, meniji bi reagovali sporije. Funkcija CreateWindows kreira glavni prozor, polje za listu i seriju prozora djece za niti.

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

Kreiranje i sinhronizacija niti


// ... return( TRUE ); }

Procedure za rad sa prozorima i porukama

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

if ( ! uRet ) { return( FALSE ); }

Kreiranje i sinhronizacija niti


// nije bilo moguce kreirati tajmer

// 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; }

Kreiranje i sinhronizacija niti

// 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;

Kreiranje i sinhronizacija niti

} 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; }

Kreiranje i sinhronizacija niti

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; }

Kreiranje i sinhronizacija niti

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

Kreiranje i sinhronizacija niti

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

Kreiranje i sinhronizacija niti

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