350 likes | 623 Views
Sistemsko programiranje. VII. poglavlje Pouzdani modeli za rad s nitima. Ciljevi. Nakon svladavanja ovog poglavlja trebali bi znati : Riješavati još kompleksnije probleme s nitima Koristiti standardne modele za rad s nitima u dizajnu aplikacije Kreirati i koristiti timer-e za čekanje
E N D
Sistemsko programiranje VII. poglavlje Pouzdani modeli za rad s nitima
Ciljevi Nakon svladavanja ovog poglavlja trebali bi znati: • Riješavati još kompleksnije probleme s nitima • Koristiti standardne modele za rad s nitima u dizajnu aplikacije • Kreirati i koristiti timer-e za čekanje • Praviti aplikacije koje su pouzdane i lake za održavanje • Razumjeti i izbjegavati mnoge uobičajene greške
Dnevni red • 1. dio Višenitni modeli • 2. dio Model uvjetne varijable • 3. dio Vremensko čekanje • 4. dio Izbjegavanje pogrešnog koda - savjeti
1. dio Višenitni modeli
Višenitni modeli • Prednosti korištenja modela za niti: • Brži dizajn i razvoj • Modeli su razumljivi i dobro iztestirani • Izbjegavju potencijalne probleme kod višenitnosti • Modeli pogađaju strukturu većine programskih problema • Vrlo jednostavni slučajevi, plus tri “klasična” modela • Gazda/šljaker • Tekuća vrpca (cjevovod, pipeline) • Klijent/Server • Osim toga i kombinirani modeli • Programiranje “u velikom”
Datoteke Baze podataka Model Gazda/Šljaker Ulaz (Stream) Program Resursi Šljakeri Zadatak1 Gazda main () Zadatak2 ... ZadatakN Izračuni
Model tekuće vrpce Poruka Log P1 C1 TO FROM DATA M1 M2 . . . M5 Q Q Q CN PN Predajnik Primatelj Q Proizvođači Potrošači
Model Klijent/Server Server Nit 1 Stog 1 TLS Klijent 1 Kod Zajednički podaci Konstante Nit-Nit komunikacija i sinkronizacija Arg Nit 1 Specifični podaci TLS Nadgledanje statusa i upravljanje Server Nit N Stog 2 Stog TLS Klijent N Arg Arg Nit N Specifični podaci Statistike
Kombinacije • Veliki sustavi su rijetko čisti primjerci jednog od modela • Faze u tekućoj vrpci često su implementirane kao gazda/šljaker sistemi • Server može posluživati zahtjeve klijenata po modelu tekuće vrpce • Uvijek koristiti dobre implementacije dokumentirajući pojedinačne module u terminima ovih modela • Lakše za održavanje, razumijevanje i otklanjanje grešaka
2. dio Model uvjetne varijable
Model uvjetne varijable • Uvjetne varijable (conditional variable, CV) • Programiranje “u malom” • Koncepti preuzeti iz Pniti (POSIX niti, slijedi kasnije) • Nekoliko ključnih elemenata: • Struktura podataka tipa STATE_TYPE • Sadrži sve podatke kao što su poruke, sume za provjeru (checksums), itd. • Mutex i jedan ili više pridruženih događaja • Mutex štiti strukturu podataka • Događaji signaliziraju “bitne” promjene stanja strukture podataka • Boolove funkcijske izračune - “predikati uvjetne varijable” • Primjerice, “nova poruka je spremna” • Događaj je pridružen svakom predikatu slučajne varijable • Događaj se signalizira kada predikat uvjetne varijable postane istina
Model uvjetne varijable: podaci Dijeljena struktura podataka: typedef struct _state_t { HANDLE Guard; /* Muteks koji štiti objekt */ HANDLE cvpSet; /* Auto-reset događaj */ . . . druge uvjetne varijable /* Struktura stanja s brojačima, itd. */ struct STATE_VAR_TYPE StateVar; } STATE_TYPE State; . . . /* Inicijaliziranje stanja, kreiranje muteksa i događaja */ . . .
Model uvjetne varijable: proizvođač /* nit PROIZVOĐAČ koja modificira stanje */ WaitForSingleObject (State.Guard, INFINITE); /* Promjeni stanje tako da predikat CV vrijedi */ . . . State.StateVar.xyz = . . . ; SetEvent (State.cvpSet); ReleaseMutex (State.Guard); /* Kraj bitnog dijela kod proizvođača */ . . .
Model uvjetne varijable: potrošač /* Nit POTROŠAČ čeka na određeno stanje */ WaitForSingleObject (State.Guard, INFINITE); while (!cvp(&State)) { ReleaseMutex (State.Guard); WaitForSingleObject (State.CvpSet, TimeOut); WaitForSingleObject (State.Guard, INFINITE); } . . . ReleaseMutex (State.Guard); /* Kraj bitnog dijela kod potrošača */
Komentari modela uvjetne varijable • Moramo ponavljati petlju i testirati • Timeout je konačan – parametar za podešavanje izvedbe • Ovaj model izbjegava propuštene signale i druge probleme • Ovo je verzija koja “odašilje” (“broadcast”) • Više potrošača otpušta se s jednim proizvođačevim SetEvent() • Ponašanje u stvarnoj primjeni ovisi o cvp() i značenju proizvodnje/potrošnje • Koristi se auto-reset događaj i SetEvent() • “Signalna” verzija koristi manual-reset događaj i PulseEvent() • Samo jedan potrošač je otpušten
SignalObjectAndWait (1/2) • Tri osnovna koraka petlje kod potrošača • Otključaj muteks • Čekaj na događaj • Zaključaj muteks ponovno • Prva dva nisu potpuno jednostavna • Proizvođač može signalizirati prije nego potrošač čeka • Rizik: propušteni signal – zbog toga imamo timeout (točno vrijeme čekanja) • SignalObjectAndWait() kombinira prva dva koraka • Jedna jednostavna funkcija • Pniti kombiniraju sva tri koraka • >= NT 4.0 (nema na Win 9x, Me)
SignalObjectAndWait (2/2) DWORD SignalObjectAndWait( HANDLE hObjectToSignal, // Muteks HANDLE hObjectToWaitOn, // Događaj DWORD dwMilliseconds, // timeout u ms BOOL bAlertable )// Koristiti FALSE • SOAW je opće namjene • Ovdje pokazujemo samo upotrebu muteks-događaj • Timeout često puta može biti i beskonačan • Treba biti siguran da je: #define _WIN32_WINNT 0x400 // WINBASE.H
Korištenje modela uvjetne varijable • Proizvođač zaključa muteks, promjeni stanje, postavi događaj i otključa muteks • Događaj se mora postaviti sa zaključanim muteksom • Potrošač testira predikat uvjetne varijable sa zaključanim muteksom • Ako predikat nije istina, potrošač mora otključati muteks prije čekanja na događaj • Čekanje na događaj mora imati timeout – da se izbjegne propušteni signal • Osima ako koristite SOAW! • Potrošač uvijek resetira predikat nakon čekanja na događaj • Potrošač je uvijek vlasnik muteksa kada izlazi iz petlje • i ako se tijelo petlje izvodi i ako ne
Varijacije modela uvjetne varijable • Unutar proizvođač/potrošač koda, više niti je oslobođeno • Auto-reset događaj • SetEvent • Ili, možda postoji samo jedna poruka, a više niti potrošača • Događaj treba biti manual-reset • Proizvođač treba pozvati PulseEvent • Osigurati da se točno jedna nit oslobodi čekanja
Ako smo zainteresirani za sljedeći događaj /* Nit POTROŠAČ čeka na SLJEDEĆU promjenu stanja */ WaitForSingleObject (State.Guard, INFINITE); do { SignalObjectAndWait (State.Guard, State.cvpSet, TimeOut, FALSE); WaitForSingleObject (State.Guard, INFINITE); } while (!cvp(&State)); /* Nit sada posjeduje muteks, a cvp(&State) je istinit */ /* Poduzmi odgovarajuću akciju, možda promjena stanja */ . . . ReleaseMutex (State.Guard); /* kraj bitnog dijela kod potrošača */
3. dio Vremensko čekanje
Vremensko čekanje (1/2) • Tip objekata jezgre za čekanje >= Win2K • Za periodično izvođenje zadaća ili točno u određeno vrijeme • Kreiranje našeg vremenskog signala: • Kreira se vremenska nit • Nit postavlja događaj nakon “buđenja” iz funkcije Sleep • Dva tipa: • Vremenska sinkronizacija: Koristi funkciju koja završava – mora biti u stanju mogućeg uzbunjivanja (alertable) • Manual reset vremenska notifikacija: čeka na handle timer-a
Vremensko čekanje (2/2) HANDLE CreateWaitableTimer( LPSECURITY_ATTRIBUTES lpTimerAttributes, BOOL bManualReset, // Manual reset state LPCTSTR lpTimerName // *timer object name ); • Timer je inicijalno neaktivan, ali se kasnije aktivira • Imenovan, zato postoji i funkcija OpenWaitableTimer
Postavljanje timer-a za čekanje BOOL SetWaitableTimer( HANDLE hTimer, const LARGE_INTEGER *pDueTime, LONG lPeriod, // periodički interval PTIMERAPCROUTINE pfnCompletionRoutine, LPVOID lpArgToCompletionRoutine, BOOL fResume /* zastavica za stanje ponovnog nastavljanja */ );
Otkazivanje timer-a za čekanje CancelWaitableTimer otkazuje zadnji pozvani SetWaitableTimer • Neće promjeniti signalizirano stanje timer-a • Koristiti drugi put SetWaitableTimer ako to želimo BOOL CancelWaitableTimer( HANDLE hTimer );
Funkcija za čekanje s mogućnosti uzbunjivanja (alertable) • Opcija kod više funkcija za čekanje – Najčešće “Ex” verzije DWORD SleepEx( DWORD dwMilliseconds, BOOL bAlertable ); • Vraća čim se: • završi vrijeme čekanja • ako je bAlerable postavljen, i izvršen je poziv primjerice WaitForSingleObjectEx ( …, BOOL bAlertable); WaitForMultipleObjectsEx (…, BOOL bAlertable);
4. dio Izbjegavanje pogrešnog koda - savjeti
Izbjegavanje pogrešnog koda - savjeti (1/3) • Obratiti pažnju na dizajn, implementaciju i korištenje poznatih programskih modela • Najbolja tehnika za ‘debagiranje’ : otpočetka ne kreirati bagove • Mnogi ozbiljni defekti će prouzrokovati najšire i najskuplje testiranje • Debagiranje mijenja vremensko ponašanje • Tako se maskiraju “uvjeti utrke” koji mi želimo postići, tj. ne vidimo ono što želimo vidjeti
Izbjegavanje pogrešnog koda - savjeti (2/3) • Izbjegavati oslanjanje na unutarnju strukturu niti • Nikada se ne treba kladiti “koja će nit prije” • Zakazivanje za izvršenje (Scheduling) nije isto što i sinkronizacija • Mogu se pojaviti višestruke “utrke” za podacima • I onda kada koristimo muteksa za zaštitu zajedničkih podataka • Izbjegavati totalne zastoje misleći kakvo će biti zajedničko funkcioniranje • Nikada ne dijeliti događaje između predikata
Izbjegavanje pogrešnog koda - savjeti (3/3) • Oprez kod dijeljenja stogova i s time vezane korupcije memorijskog prostora • Oprez: nema upozorenja kada se stog niti prepuni • Treba koristiti volatile modifikator spremanja varijabli • Koristiti model uvjetnih varijabli kako treba • Razumjeti invarijante i predikate uvjetne varijable • Neka bude što jednostavnije • Testirati na više sistema (jedno- i više-procesorskim) • Testiranje je neophodno ali ne i dovoljno • Treba biti spreman na neugodna iznenađenja
Primjedbe oko blokiranja niti • U bilo kojem trenutku nit može otići u stanje “spavanja” na neograničen vremenski period • Ne postoji uređenje između niti, osim ako ga mi ne uvedemo • Zakazivanje niti za izvršavanje može biti jako različito • Različite izvedbe operacijskih sustava • Različiti modeli istog sustava • Sustavi s različitim brzinama procesora • Eksplicitno sinkroniziranje je uvijek potrebno • Nema vremena čekanja (timeout) kod poziva pthread_join()
Cijepanje riječi (1/2) • Jedan muteks treba koristiti u svakom kritičnom odsječku povezanim s dijeljenim resursom • poznato i kao “borba oko cache linije” - SMP • Primjer: procesor može pristupiti četveroriječnoj memorijskoj jedinici • 64 bita, jer je riječ 16 bitna • 16-bitne riječi nisu odvojeni resursi • kad različite niti pristupaju različitim vrijednostima koje leže u istim četveroriječnim jedinicama memorije, tada imamo cijepanje, tj. borbu za riječ • Cijela četvero-riječ treba biti zaštičena s jednim muteksom
11 11 11 11 11 51 22 22 22 22 22 22 33 33 33 33 33 33 44 44 48 44 44 48 Cijepanje riječi (2/2) Memorija 4 16- Bitne riječi Nit A Nit B Čita Čita Piše u prvi bajt Piše na zadnji bajt Završava Završava Vrijednost Niti B pobjeđuje; Idući puta Nit A može pobjediti
Izbjegavanje totalnog zastoja, hijerarhija zaključavanja • Izbjegavati totalni zastoj koristeći hijerarhiju zaključavanja gdje ćemo mutekse uvijek zaključavati po istom redoslijedu • Otključavati treba u obrnutom redosljedu • Dodatne primjedbe kod totalnog zastoja • Tijekom razvoja i otklanjana grešaka • Osigurati da je višestruko zaključavanje uvijek u istom redoslijedu • Dokumentirati hijerarhiju zaključavanja • Hijerahija zaključavanja je dokazano bez totalnog zastoja
Nema totalnog zastoja s hijerahijskim zaključavanjem AddSharedElement Nit DeleteSharedElement Nit OS SCHEDULER Izvodi se Spremna Lock (&ListA.guard) Run Prebaci izvođenje Running Spremna Blokiranje Lock (&ListA.guard) Run Lock (&ListB.guard) Izvodi se Blokirana Unock (&ListB.guard) Unlock (&ListA.guard) Deblokiranje Spremna Run Prebaci izvođenje Lock (&ListB.guard) Spremna Izvodi se Unock (&ListB.guard) Unlock (&ListA.guard)