Orodja za sinhronizacijo niti v operacijskem sistemu Windows (kritični odseki, muteksi, semaforji, dogodki). Sinhronizacijski objekti v sistemu Windows Sinhronizacija procesov z uporabo dogodkov

Predavanje št. 9. Sinhronizacija procesov in niti

1. Cilji in sredstva sinhronizacije.

2. Sinhronizacijski mehanizmi.

1. Cilji in sredstva sinhronizacije

Obstaja precej širok razred orodij operacijskega sistema, ki zagotavljajo medsebojno sinhronizacijo procesov in niti. Potreba po sinhronizaciji niti se pojavi samo v večprogramskem operacijskem sistemu in je povezana s skupno uporabo strojne opreme in informacijskih virov računalniškega sistema. Sinhronizacija je potrebna, da se izognemo dirkam in zastojem pri izmenjavi podatkov med nitmi, skupni rabi podatkov ter dostopanju do procesorja in V/I naprav.

V mnogih operacijskih sistemih se ta orodja imenujejo medprocesna komunikacijska orodja (IPC), kar odraža zgodovinski primat koncepta "proces" v odnosu do koncepta "nit". Običajno orodja IPC vključujejo ne le orodja za medprocesno sinhronizacijo, temveč tudi orodja za medprocesno izmenjavo podatkov.

Izvajanje niti v multiprogramskem okolju je vedno asinhrono. Zelo težko je s popolno gotovostjo reči, v kateri fazi izvajanja bo proces v določenem trenutku. Tudi v enoprogramskem načinu ni vedno mogoče natančno oceniti časa, ki je potreben za dokončanje naloge. Ta čas je v mnogih primerih bistveno odvisen od vrednosti izvornih podatkov, kar vpliva na število ciklov, smer razvejanja programa, čas izvajanja V/I operacij itd. Ker izvorni podatki v različnih časih, ko je naloga zagnana je lahko različna, zato je lahko tudi čas izvedbe posameznih faz in naloge kot celote zelo negotova vrednost.


Še bolj negotov je čas izvajanja programa v večprogramskem sistemu. Trenutki, ko so niti prekinjene, čas, ki ga preživijo v čakalnih vrstah za skupne vire, vrstni red, v katerem so niti izbrane za izvedbo - vsi ti dogodki so rezultat sotočja številnih okoliščin in jih je mogoče razlagati kot naključne. V najboljšem primeru je mogoče oceniti verjetnostne značilnosti računalniškega procesa, na primer verjetnost njegovega dokončanja v določenem časovnem obdobju.

Tako niti v splošnem primeru (ko programer ni sprejel posebnih ukrepov za njihovo sinhronizacijo) tečejo neodvisno, asinhrono druga z drugo. To velja tako za niti v istem procesu, ki izvajajo skupno programsko kodo, kot za niti v različnih procesih, od katerih vsaka izvaja svoj program.

Vsaka interakcija med procesi ali nitmi je povezana z njihovimi sinhronizacija, ki je sestavljen iz usklajevanja njihovih hitrosti tako, da prekine tok do nastopa določenega dogodka in ga nato aktivira, ko se ta dogodek zgodi. Sinhronizacija je jedro vsake interakcije niti, ne glede na to, ali vključuje skupno rabo virov ali izmenjavo podatkov. Na primer, sprejemna nit bi morala dostopati do podatkov šele, ko jih je v medpomnilnik poslala pošiljajoča nit. Če je sprejemna nit dostopala do podatkov, preden je vstopila v medpomnilnik, jo je treba začasno ustaviti.

Pri souporabi virov strojne opreme je nujno potrebna tudi sinhronizacija. Ko na primer aktivna nit potrebuje dostop do serijskih vrat in druga nit, ki je trenutno v stanju čakanja, dela s temi vrati v ekskluzivnem načinu, OS prekine aktivno nit in je ne aktivira, dokler vrata, ki jih potrebuje je brezplačen. Pogosto je potrebna tudi sinhronizacija z dogodki zunaj računalniškega sistema, na primer reakcija na pritisk kombinacije tipk Ctrl+C.

Vsako sekundo se v sistemu zgodi na stotine dogodkov, povezanih z dodeljevanjem in sproščanjem virov, OS pa mora imeti zanesljiva in učinkovita sredstva, ki mu omogočajo sinhronizacijo niti z dogodki, ki se dogajajo v sistemu.

Za sinhronizacijo niti aplikacijskih programov lahko programer uporablja svoja orodja in tehnike za sinhronizacijo ter orodja operacijskega sistema. Na primer, dve niti istega aplikacijskega procesa lahko uskladita svoje delo z uporabo globalne logične spremenljivke, ki je na voljo obema in je nastavljena na ena, ko pride do nekega dogodka, na primer ena nit proizvede podatke, potrebne za drugo, da nadaljuje z delom. Vendar pa so v mnogih primerih sinhronizacijske zmogljivosti, ki jih operacijski sistem omogoča v obliki sistemskih klicev, bolj učinkovite ali celo edine možne. Tako niti, ki pripadajo različnim procesom, ne morejo na noben način posegati v delo druga druge. Brez posredovanja operacijskega sistema se ne moreta medsebojno zaustaviti ali obvestiti o pojavu dogodka. Orodja za sinhronizacijo operacijski sistem uporablja ne samo za sinhronizacijo aplikacijskih procesov, ampak tudi za svoje notranje potrebe.

Običajno razvijalci operacijskih sistemov nudijo široko paleto orodij za sinhronizacijo, ki so na voljo programerjem aplikacij in sistemov. Ta orodja lahko tvorijo hierarhijo, ko so kompleksnejša zgrajena na podlagi enostavnejših orodij, pa tudi funkcionalno specializirana, na primer orodja za sinhronizacijo niti enega procesa, orodja za sinhronizacijo niti različnih procesov pri izmenjavi podatkov itd. Pogosto se funkcionalnost različnih sinhronizacij sistemskih klicev prekriva, tako da lahko programer uporabi več klicev za rešitev ene težave, odvisno od njegovih osebnih preferenc.


Potreba po sinhronizaciji in dirki

Zanemarjanje težav s sinhronizacijo v večnitnem sistemu lahko povzroči nepravilno rešitev težave ali celo zrušitev sistema. Upoštevajte na primer (slika 4.16) nalogo vzdrževanja baze podatkov strank določenega podjetja. Vsaki stranki je v bazi dodeljen ločen zapis, ki med drugim vsebuje polji Naročilo in Plačilo. Program, ki vzdržuje bazo podatkov, je zasnovan kot enoten proces, ki ima več niti, med drugim nit A, ki v bazo podatkov vnaša podatke o prejetih naročilih strank, in nit B, ki v bazo beleži podatke o plačilih strank za račune. Obe niti delujeta skupaj v skupni datoteki zbirke podatkov z istimi algoritmi, ki vključujejo tri korake.

2. Vnesite novo vrednost v polje Naročilo (za tok A) ali Plačilo (za tok B).

3. Vrnite spremenjeni zapis v datoteko baze podatkov.

https://pandia.ru/text/78/239/images/image002_238.gif" width="505" height="374 src=">

riž. 4.17. Vpliv relativnih hitrosti toka na rezultat reševanja problema

Kritični del

Pomemben koncept pri sinhronizaciji niti je koncept "kritičnega odseka" programa. Kritični del je del programa, katerega rezultat izvajanja se lahko nepredvidljivo spremeni, če druge niti spremenijo spremenljivke, povezane s tem delom programa, medtem ko izvajanje tega dela še ni dokončano. Kritični odsek je vedno definiran glede na določeno kritični podatkiče se spremeni na neusklajen način, se lahko pojavijo neželeni učinki. V prejšnjem primeru so bili kritični podatki zapisi datoteke baze podatkov. Vse niti, ki delajo s kritičnimi podatki, morajo imeti definiran kritični razdelek. Upoštevajte, da je v različnih nitih kritični del na splošno sestavljen iz različnih zaporedij ukazov.

Da bi odpravili učinek dirkanja na kritične podatke, je treba zagotoviti, da je v kritičnem razdelku, povezanem s temi podatki, kadar koli samo ena nit. Ni pomembno, ali je ta nit v aktivnem ali suspendiranem stanju. Ta tehnika se imenuje medsebojno izključevanje. Operacijski sistem uporablja različne načine za implementacijo medsebojnega izključevanja. Nekatere metode so primerne za medsebojno izključevanje, ko samo niti enega procesa vstopijo v kritični del, druge pa lahko zagotovijo medsebojno izključevanje niti različnih procesov.

Najenostavnejši in hkrati najneučinkovitejši način zagotavljanja medsebojne izključitve je, da operacijski sistem dovoli niti, da onemogoči morebitne prekinitve, medtem ko je v kritičnem delu. Vendar se ta metoda praktično ne uporablja, saj je nevarno zaupati uporabniški niti za nadzor sistema - lahko dolgo časa zasede procesor in če se nit zruši v kritičnem delu, se bo zrušil celoten sistem, ker prekinitve ne bodo nikoli dovoljene.

2. Sinhronizacijski mehanizmi.

Blokiranje spremenljivk

Za sinhronizacijo niti enega procesa lahko aplikacijski programer uporabi global blokirne spremenljivke. Programer dela s temi spremenljivkami, do katerih imajo vse niti procesa neposreden dostop, ne da bi se zatekel k sistemskim klicem OS.

Kar bi onemogočilo prekinitve skozi celotno operacijo preverjanja in namestitve.

Implementacija medsebojnega izključevanja na zgoraj opisani način ima pomembno pomanjkljivost: v času, ko je ena nit v kritičnem odseku, bo druga nit, ki potrebuje isti vir in ima dostop do procesorja, nenehno preverjala blokirno spremenljivko, s čimer bo zapravljala čas procesorja. ki mu je dodeljena, kar bi lahko uporabili za izvajanje druge niti. Za odpravo te pomanjkljivosti številni operacijski sistemi ponujajo posebne sistemske klice za delo s kritičnimi odseki.

Na sl. Slika 4.19 prikazuje, kako te funkcije izvajajo medsebojno izključevanje v operacijskem sistemu Windows NT. Preden začne spreminjati kritične podatke, nit izvede sistemski klic EnterCriticalSection(). Ta klic najprej izvede, tako kot v prejšnjem primeru, preverjanje blokirne spremenljivke, ki odraža stanje kritičnega vira. Če sistemski klic ugotovi, da je vir zaseden (F(D) = 0), za razliko od prejšnjega primera ne izvede ciklične ankete, ampak postavi nit v stanje čakanja (D) in zabeleži, da ta nit je treba aktivirati, ko bo ustrezen vir na voljo. Nit, ki trenutno uporablja ta vir, mora po izhodu iz kritičnega odseka izvesti sistemsko funkcijo LeaveCriticalSectionO, zaradi česar blokirna spremenljivka prevzame vrednost, ki ustreza prostemu stanju vira (F(D) = 1), in operacijski sistem pregleda čakalno vrsto tistih, ki čakajo na to nit virov, in premakne prvo nit iz čakalne vrste v stanje pripravljenosti.

Režijski stroški" href="/text/category/nakladnie_rashodi/" rel="bookmark">Režijski stroški OS za izvedbo funkcije vstopa in izstopa iz kritičnega dela lahko presežejo dosežene prihranke.

Semaforji

Posplošitev blokirnih spremenljivk so ti Dijkstra semaforji. Namesto binarnih spremenljivk je Dijkstra predlagal uporabo spremenljivk, ki lahko zavzamejo nenegativne celoštevilske vrednosti. Takšne spremenljivke, ki se uporabljajo za sinhronizacijo računalniških procesov, imenujemo semaforji.

Za delo s semaforji sta uvedena dva primitiva, tradicionalno označena s P in V. Naj spremenljivka S predstavlja semafor. Nato sta akciji V(S) in P(S) definirani na naslednji način.

* V(S): Spremenljivka S se poveča za 1 kot eno samo dejanje. Vzorčenja, gradnje in shranjevanja ni mogoče prekiniti. Med izvajanjem te operacije druge niti ne dostopajo do spremenljivke S.

* P(S): Zmanjša S za 1, če je mogoče. Če je 5=0 in je nemogoče zmanjšati S, medtem ko ostane v območju vrednosti nenegativnih celih števil, potem operacija klica niti P čaka, dokler to zmanjšanje ne postane možno. Uspešno preverjanje in zmanjšanje je tudi nedeljiva operacija.

Med izvajanjem primitivov V in P niso dovoljene prekinitve.

V posebnem primeru, ko lahko semafor S sprejme samo vrednosti 0 in 1, postane blokirna spremenljivka, ki se zaradi tega pogosto imenuje binarni semafor. Dejavnost P lahko postavi nit, ki jo izvaja, v stanje čakanja, medtem ko lahko dejavnost V v nekaterih okoliščinah prebudi drugo nit, ki jo je prekinila dejavnost P.

Oglejmo si uporabo semaforjev na klasičnem primeru interakcije dveh niti, ki delujeta v večprogramskem načinu, od katerih ena zapisuje podatke v medpomnilniško področje, druga pa jih bere iz medpomnilniškega področja. Naj bo vmesni pomnilnik sestavljen iz N medpomnilnikov, od katerih lahko vsak vsebuje en vnos. Na splošno imata zapisovalna in bralna nit lahko različne hitrosti in dostopa do vmesnega prostora z različno intenzivnostjo. V enem obdobju lahko hitrost pisanja preseže hitrost branja, v drugem - obratno. Za pravilno sodelovanje se mora zapisovalna nit začasno ustaviti, ko so vsi medpomnilniki zasedeni, in se zbuditi, ko se sprosti vsaj en medpomnilnik. Nasprotno pa bi se morala bralna nit začasno ustaviti, ko so vsi medpomnilniki prazni, in se zbuditi, ko se pojavi vsaj en zapis.

Predstavimo dva semaforja: e - število praznih medpomnilnikov in f - število napolnjenih medpomnilnikov in v začetnem stanju e = N, a f = 0. Nato lahko delovanje niti s skupnim medpomnilniškim poljem opišemo takole (slika 4.20).

Nit zapisovalnika najprej izvede operacijo P(e), s katero preveri, ali so v medpomnilniškem področju prazni medpomnilniki. V skladu s semantiko operacije P, če je semafor e enak 0 (to pomeni, da trenutno ni prostih vmesnih pomnilnikov), potem zapisovalna nit preide v stanje čakanja. Če je vrednost e pozitivno število, potem zmanjša število prostih medpomnilnikov, zapiše podatke v naslednji prosti medpomnilnik in nato z operacijo V(f) poveča število zasedenih medpomnilnikov. Nit bralnika deluje na podoben način, s to razliko, da začne s preverjanjem polnih medpomnilnikov in po branju podatkov poveča število prostih medpomnilnikov.

DIV_ADBLOCK860">

Semafor se lahko uporablja tudi kot blokirna spremenljivka. V zgoraj obravnavanem primeru bomo za odpravo kolizij pri delu z območjem skupnega pomnilnika predpostavili, da sta pisanje v medpomnilnik in branje iz njega kritična odseka. Medsebojno izključitev bomo zagotovili z uporabo binarnega semaforja b (slika 4.21). Obe niti morata po preverjanju razpoložljivosti medpomnilnikov preveriti razpoložljivost kritičnega odseka.

https://pandia.ru/text/78/239/images/image007_110.jpg" width="495" height="639 src=">

riž. 4.22. Pojav zastojev med izvajanjem programa

OPOMBA

Zastoje je treba razlikovati od preprostih čakalnih vrst, čeprav se oba pojavita, ko so viri v skupni rabi in so videti podobno: nit je začasno ustavljena in čaka, da se vir sprosti. Vendar pa je čakalna vrsta normalen pojav, neločljiv znak visoke izkoriščenosti virov, ko zahteve prispejo naključno. Čakalna vrsta se pojavi, ko vir trenutno ni na voljo, vendar bo čez nekaj časa sproščena, kar bo omogočilo nadaljevanje izvajanja niti. Zastoj, kot že ime pove, je nekoliko nerešljiva situacija. Nujni pogoj za pojav zastoja je, da nit potrebuje več virov hkrati.

V obravnavanih primerih je zastoj nastal zaradi dveh niti, vendar se lahko več niti medsebojno blokira. Na sl. Na sliki 2.23 je prikazana taka porazdelitev virov Ri med več nitmi Tj, ki je vodila do pojava zastojev. Puščice označujejo zahteve po virih toka. Polna puščica pomeni, da je bil ustrezen vir dodeljen niti, pikčasta puščica pa povezuje nit z virom, ki je potreben, vendar ga še ni mogoče dodeliti, ker ga zaseda druga nit. Na primer, nit T1 potrebuje sredstva R1 in R2 za opravljanje dela, od katerih je dodeljen samo eden - R1, vir R2 pa ima nit T2. Nobena od štirih niti, prikazanih na sliki, ne more nadaljevati svojega dela, ker nima vseh potrebnih sredstev za to.

Nezmožnost niti, da dokončajo začeto delo zaradi zastojev, zmanjša zmogljivost računalniškega sistema. Zato se veliko pozornosti posveča problemu preprečevanja zastojev. V primeru, da do zastoja vendarle pride, mora sistem operaterju operaterju zagotoviti sredstvo, s katerim lahko prepozna zastoj in ga loči od običajne blokade zaradi začasne nedostopnosti virov. Končno, če je diagnosticiran zastoj, so potrebna sredstva za odstranitev zastoja in obnovitev normalnega računalniškega procesa.

Lastnik" href="/text/category/vladeletc/" rel="bookmark">lastnik, ki ga nastavi na nesignalizirano stanje, in vstopi v kritični razdelek. Ko nit zaključi delo s kritičnimi podatki, daje up" mutex in ga nastavi v signalizirano stanje. V tem trenutku je mutex prost in ne pripada nobeni niti. Če katera koli nit čaka na sprostitev, potem postane naslednji lastnik tega mutexa, pri istočasno gre mutex v nesignalizirano stanje.

Objekt dogodka (v tem primeru se beseda "dogodek" uporablja v ožjem pomenu, kot oznaka določene vrste sinhronizacijskega objekta) se običajno uporablja ne za dostop do podatkov, temveč za obveščanje drugih niti, da so se nekatera dejanja končala. Recimo, da je v neki aplikaciji delo organizirano tako, da ena nit bere podatke iz datoteke v pomnilniški medpomnilnik, druge niti pa te podatke obdelajo, nato prva nit prebere nov del podatkov, druge niti pa znova obdelati in tako naprej. Na začetku izvajanja prva nit nastavi objekt dogodka v nesignalizirano stanje. Vse druge niti so opravile klic Wait(X), kjer je X kazalec dogodka, in so v začasno ustavljenem stanju in čakajo, da se ta dogodek zgodi. Takoj ko je medpomnilnik poln, prva nit to sporoči operacijskemu sistemu s klicem Set(X). Operacijski sistem pregleda čakalno vrsto čakajočih niti in aktivira vse niti, ki čakajo na ta dogodek.

Signali

Signal Omogoča, da se opravilo odzove na dogodek, katerega vir je lahko operacijski sistem ali drugo opravilo. Signali vključujejo prekinitev opravila in izvajanje vnaprej določenih dejanj. Signali so lahko generirani sinhrono, torej kot rezultat dela samega procesa, ali pa jih procesu pošlje drug proces, torej generirani asinhrono. Sinhroni signali najpogosteje prihajajo iz prekinitvenega sistema procesorja in označujejo dejanja procesa, ki jih strojna oprema blokira, kot je deljenje z ničlo, napaka pri naslavljanju, kršitev zaščite pomnilnika itd.

Primer asinhronega signala je signal iz terminala. Mnogi operacijski sistemi omogočajo takojšnjo odstranitev procesa iz izvajanja. Za to lahko uporabnik pritisne določeno kombinacijo tipk (Ctrl+C, Ctrl+Break), zaradi česar OS ustvari signal in ga pošlje aktivnemu procesu. Signal lahko prispe kadar koli med izvajanjem procesa (to je asinhrono), kar zahteva, da se proces takoj zaključi. V tem primeru je odgovor na signal brezpogojno dokončanje procesa.

V sistemu je mogoče definirati niz signalov. Programska koda procesa, ki je prejel signal, ga lahko ignorira ali se nanj odzove s standardnim dejanjem (na primer izhodom) ali izvede posebna dejanja, ki jih določi aplikacijski programer. V slednjem primeru je treba v programski kodi predvideti posebne sistemske klice, s pomočjo katerih operacijski sistem obvesti, kateri postopek naj se izvede kot odgovor na prejem določenega signala.

Signali zagotavljajo logično komunikacijo med procesi ter med procesi in uporabniki (terminali). Ker je za pošiljanje signala potrebno poznavanje identifikatorja procesa, je interakcija preko signalov mogoča le med sorodnimi procesi, ki lahko pridobivajo informacije o identifikatorjih drug drugega.

V porazdeljenih sistemih, sestavljenih iz več procesorjev, od katerih ima vsak svoj RAM, so zaklepne spremenljivke, semaforji, signali in druge podobne funkcije, ki temeljijo na skupnem pomnilniku, neprimerne. V takšnih sistemih je mogoče sinhronizacijo doseči le z izmenjavo sporočil.

Proces je primerek programa, naloženega v pomnilnik. Ta primerek lahko ustvari niti, ki so zaporedje navodil za izvedbo. Pomembno je razumeti, da se ne izvajajo procesi, temveč niti.

Poleg tega ima vsak proces vsaj eno nit. Ta nit se imenuje glavna (glavna) nit aplikacije.

Ker je skoraj vedno veliko več niti, kot je fizičnih procesorjev, ki jih izvajajo, se niti dejansko ne izvajajo hkrati, ampak po vrsti (procesorski čas je porazdeljen med nitmi). Toda preklapljanje med njimi se zgodi tako pogosto, da se zdi, da tečejo vzporedno.

Odvisno od situacije so lahko niti v treh stanjih. Prvič, nit se lahko izvaja, ko ji je dodeljen čas procesorja, tj. morda je v stanju aktivnosti. Drugič, morda je neaktiven in čaka na dodelitev procesorja, tj. biti v stanju pripravljenosti. In obstaja še tretje, prav tako zelo pomembno stanje – stanje blokade. Ko je nit blokirana, ji sploh ni dodeljen čas. Običajno se blok postavi med čakanjem na določen dogodek. Ko pride do tega dogodka, se nit samodejno premakne iz blokiranega stanja v stanje pripravljenosti. Na primer, če ena nit izvaja izračune, druga pa mora počakati na rezultate, da jih shrani na disk. Drugi bi lahko uporabil zanko, kot je "while(!isCalcFinished) continue;", vendar je v praksi enostavno preveriti, da je med izvajanjem te zanke procesor 100% zaseden (to se imenuje aktivno čakanje). Če je le mogoče, se je treba izogibati takim ciklom, pri katerih je zaklepni mehanizem neprecenljiva pomoč. Druga nit se lahko blokira, dokler prva nit ne sproži dogodka, ki označuje, da je branje končano.

Sinhronizacija niti v operacijskem sistemu Windows

Windows izvaja preventivno večopravilnost - to pomeni, da lahko sistem kadar koli prekine izvajanje ene niti in prenese nadzor na drugo. Prej, v sistemu Windows 3.1, je bila uporabljena organizacijska metoda, imenovana kooperativna večopravilnost: sistem je čakal, da je nit sama prenesla nadzor nanj, zato je bilo treba računalnik znova zagnati, če je ena aplikacija zamrznila.

Vse niti, ki pripadajo istemu procesu, imajo skupne vire - na primer naslovni prostor RAM ali odprte datoteke. Ti viri pripadajo celotnemu procesu in torej vsaki njegovi niti. Zato lahko vsaka nit deluje s temi viri brez kakršnih koli omejitev. Toda ... Če ena nit še ni končala dela z nekim virom v skupni rabi in sistem preklopi na drugo nit z istim virom, potem je lahko rezultat dela teh niti zelo drugačen od predvidenega. Takšni konflikti lahko nastanejo tudi med nitmi, ki pripadajo različnim procesom. Do te težave pride vedno, ko si dve ali več niti deli kateri koli vir v skupni rabi.

Primer. Nesinhronizirane niti: Če začasno zaustavite izhodno nit (pavza), se bo nit populacije polja v ozadju izvajala še naprej.

#vključi #vključi int a; ROČAJ hThr; nepodpisan dolg uThrID; void Thread(void* pParams) ( int i, num = 0; medtem ko (1) ( for (i=0; i<5; i++) a[i] = num; num++; } } int main(void) { hThr=CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)Thread,NULL,0,&uThrID); while(1) printf("%d %d %d %d %d\n", a, a, a, a, a); return 0; }

Zato je potreben mehanizem, ki bo niti omogočil, da uskladijo svoje delo s skupnimi viri. Ta mehanizem se imenuje mehanizem za sinhronizacijo niti.

Ta mehanizem je nabor objektov operacijskega sistema, ki so ustvarjeni in upravljani programsko, so skupni vsem nitim v sistemu (nekatere si delijo niti, ki pripadajo istemu procesu) in se uporabljajo za usklajevanje dostopa do virov. Viri so lahko vse, kar si lahko delita dve ali več niti - diskovna datoteka, vrata, vnos v bazo podatkov, objekt GDI in celo globalna programska spremenljivka (do katere lahko dostopajo niti, ki pripadajo istemu procesu).

Obstaja več objektov za sinhronizacijo, med katerimi so najpomembnejši mutex, kritični odsek, dogodek in semafor. Vsak od teh objektov izvaja svojo metodo sinhronizacije. Tudi sami procesi in niti se lahko uporabljajo kot sinhronizacijski objekti (ko ena nit čaka na dokončanje druge niti ali procesa); kot tudi datoteke, komunikacijske naprave, vnos v konzolo in obvestila o spremembah.

Vsak sinhronizacijski objekt je lahko v tako imenovanem signalnem stanju. Za vsako vrsto predmeta ima to stanje drugačen pomen. Niti lahko preverijo trenutno stanje objekta in/ali čakajo na spremembo tega stanja in tako uskladijo svoja dejanja. To zagotavlja, da ko nit deluje s sinhronizacijskimi objekti (jih ustvari, spremeni stanje), sistem ne bo prekinil njenega izvajanja, dokler ne dokonča tega dejanja. Tako so vse končne operacije s sinhronizacijskimi objekti atomične (nedeljive.

Delo s sinhronizacijskimi objekti

Za ustvarjanje enega ali drugega sinhronizacijskega predmeta se pokliče posebna funkcija WinAPI tipa Create... (na primer CreateMutex). Ta klic vrne ročico za predmet (HANDLE), ki ga lahko uporabljajo vse niti, ki pripadajo temu procesu. Do sinhronizacijskega objekta je mogoče dostopati iz drugega procesa - bodisi z podedovanjem ročaja za ta objekt ali, po možnosti, s klicem funkcije za odpiranje objekta (Odpri ...). Po tem klicu bo proces prejel ročaj, ki ga je mogoče kasneje uporabiti za delo s predmetom. Objektu je treba dati ime, razen če je namenjen uporabi v enem samem procesu. Imena vseh predmetov morajo biti različna (tudi če so različnih vrst). Na primer, ne morete ustvariti dogodka in semaforja z istim imenom.

Z uporabo obstoječega deskriptorja predmeta lahko določite njegovo trenutno stanje. To se naredi s pomočjo t.i. čakajoče funkcije. Najpogosteje uporabljena funkcija je WaitForSingleObject. Ta funkcija ima dva parametra, od katerih je prvi ročaj predmeta, drugi pa časovna omejitev v ms. Funkcija vrne WAIT_OBJECT_0, če je objekt signaliziran, WAIT_TIMEOUT, če je potekla časovna omejitev, in WAIT_ABANDONED, če objekt mutex ni bil osvobojen, preden je zapustila lastniško nit. Če je časovna omejitev podana kot nič, funkcija takoj vrne rezultat, sicer čaka določen čas. Če stanje objekta postane signal pred iztekom tega časa, bo funkcija vrnila WAIT_OBJECT_0, sicer bo funkcija vrnila WAIT_TIMEOUT. Če je kot čas določena simbolna konstanta INFINITE, bo funkcija čakala neomejeno dolgo, dokler stanje objekta ne postane signal.

Zelo pomembno dejstvo je, da klic čakajoče funkcije blokira trenutno nit, tj. Medtem ko je nit v stanju mirovanja, ji ni dodeljen procesorski čas.

Kritični odseki

Objekt kritičnega odseka programerju pomaga izolirati odsek kode, kjer nit dostopa do vira v skupni rabi, in prepreči sočasno uporabo vira. Pred uporabo vira nit vstopi v kritični odsek (pokliče funkcijo EnterCriticalSection). Če katera koli druga nit nato poskuša vstopiti v isti kritični razdelek, se bo njegovo izvajanje ustavilo, dokler prva nit ne zapusti razdelka s klicem LeaveCriticalSection. Uporablja se samo za niti enega procesa. Vrstni red vstopa v kritični del ni določen.

Obstaja tudi funkcija TryEnterCriticalSection, ki preveri, ali je kritični del trenutno zaseden. Z njegovo pomočjo niti med čakanjem na dostop do vira ni mogoče blokirati, vendar opraviti nekaj uporabnih dejanj.

Primer. Sinhronizacija niti z uporabo kritičnih odsekov.

#vključi #vključi KRITIČNI_ODDELEK cs; int a; ROČAJ hThr; nepodpisan dolg uThrID; void Thread(void* pParams) ( int i, num = 0; while (1) ( EnterCriticalSection(&cs); for (i=0; i<5; i++) a[i] = num; num++; LeaveCriticalSection(&cs); } } int main(void) { InitializeCriticalSection(&cs); hThr=CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)Thread,NULL,0,&uThrID); while(1) { EnterCriticalSection(&cs); printf("%d %d %d %d %d\n", a, a, a, a, a); LeaveCriticalSection(&cs); } return 0; }

Medsebojne izključitve

Objekti medsebojne izključitve (muteksi, mutex - iz MUTual EXclusion) vam omogočajo, da uskladite medsebojno izključitev dostopa do skupnega vira. Stanje signala objekta (tj. "nastavljeno" stanje) ustreza točki v času, ko objekt ne pripada nobeni niti in ga je mogoče "zajeti". Nasprotno pa stanje "ponastavitve" (brez signala) ustreza trenutku, ko neka nit že poseduje ta objekt. Dostop do objekta je odobren, ko ga nit, ki je lastnik objekta, sprosti.

Dve (ali več) niti lahko ustvarita mutex z istim imenom s klicem funkcije CreateMutex. Prva nit dejansko ustvari mutex, naslednje pa dobijo ročaj za že obstoječi objekt. To omogoča več nitim, da pridobijo ročaj za isti mutex, s čimer programerja ne skrbi, kdo dejansko ustvari mutex. Če se uporabi ta pristop, je priporočljivo, da zastavico bInitialOwner nastavite na FALSE, sicer bo nekaj težav pri določanju dejanskega ustvarjalca muteksa.

Več niti lahko pridobi ročaj za isti mutex, kar omogoča komunikacijo med procesi. Za ta pristop je mogoče uporabiti naslednje mehanizme:

  • Podrejeni proces, ustvarjen s funkcijo CreateProcess, lahko podeduje ročico mutexa, če je bil pri ustvarjanju mutexa s funkcijo CreateMutex naveden parameter lpMutexAttributes.
  • Nit lahko pridobi dvojnik obstoječega muteksa s funkcijo DuplicateHandle.
  • Nit lahko določi ime obstoječega mutexa, ko kliče funkcije OpenMutex ali CreateMutex.

Če želite razglasiti medsebojno izjemo, ki pripada trenutni niti, morate poklicati eno od čakajočih funkcij. Nit, ki ima v lasti objekt, ga lahko znova pridobi, kolikorkrat želi (to ne bo povzročilo samozaklepanja), vendar ga mora sprostiti enako število krat s funkcijo ReleaseMutex.

Za sinhronizacijo niti enega procesa je bolj učinkovito uporabiti kritične odseke.

Primer. Sinhronizacija niti z uporabo muteksov.

#vključi #vključi HANDLE hMutex; int a; ROČAJ hThr; nepodpisan dolg uThrID; void Thread(void* pParams) ( int i, num = 0; while (1) ( WaitForSingleObject(hMutex, INFINITE); for (i=0; i<5; i++) a[i] = num; num++; ReleaseMutex(hMutex); } } int main(void) { hMutex=CreateMutex(NULL, FALSE, NULL); hThr=CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)Thread,NULL,0,&uThrID); while(1) { WaitForSingleObject(hMutex, INFINITE); printf("%d %d %d %d %d\n", a, a, a, a, a); ReleaseMutex(hMutex); } return 0; }

Dogodki

Objekti dogodkov se uporabljajo za obveščanje čakajočih niti, da se je zgodil dogodek. Obstajata dve vrsti dogodkov - z ročno in samodejno ponastavitvijo. Ročno ponastavitev izvede funkcija ResetEvent. Dogodki ročne ponastavitve se uporabljajo za obveščanje več niti hkrati. Pri uporabi dogodka samodejne ponastavitve bo samo ena čakajoča nit prejela obvestilo in nadaljevala z izvajanjem; ostale bodo še naprej čakale.

Funkcija CreateEvent ustvari objekt dogodka, SetEvent - nastavi dogodek na stanje signala, ResetEvent - ponastavi dogodek. Funkcija PulseEvent nastavi dogodek in po nadaljevanju niti, ki čakajo na ta dogodek (vse z ročno ponastavitvijo in le ena s samodejno ponastavitvijo), ga ponastavi. Če ni čakajočih niti, PulseEvent preprosto ponastavi dogodek.

Primer. Sinhronizacija niti z uporabo dogodkov.

#vključi #vključi HANDLE hEvent1, hEvent2; int a; ROČAJ hThr; nepodpisan dolg uThrID; void Thread(void* pParams) ( int i, num = 0; while (1) ( WaitForSingleObject(hEvent2, INFINITE); for (i=0; i<5; i++) a[i] = num; num++; SetEvent(hEvent1); } } int main(void) { hEvent1=CreateEvent(NULL, FALSE, TRUE, NULL); hEvent2=CreateEvent(NULL, FALSE, FALSE, NULL); hThr=CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)Thread,NULL,0,&uThrID); while(1) { WaitForSingleObject(hEvent1, INFINITE); printf("%d %d %d %d %d\n", a, a, a, a, a); SetEvent(hEvent2); } return 0; }

Semaforji

Objekt semaforja je pravzaprav objekt mutex s števcem. Ta objekt dovoli, da ga "zajame" določeno število niti. Po tem bo "zajem" nemogoč, dokler ena od niti, ki je prej "zajela" semafor, tega ne sprosti. Semaforji se uporabljajo za omejevanje števila niti, ki hkrati delajo z virom. Med inicializacijo se na objekt prenese največje število niti, po vsakem "zajemu" se števec semaforja zmanjša. Stanje signala ustreza vrednosti števca, večji od nič. Ko je števec enak nič, se šteje, da semafor ni nameščen (ponastavi).

Funkcija CreateSemaphore ustvari objekt semaforja, ki označuje njegovo največjo možno začetno vrednost, OpenSemaphore - vrne deskriptor obstoječega semaforja, semafor se zajame s čakajočimi funkcijami in vrednost semaforja se zmanjša za eno, ReleaseSemaphore - semafor se sprosti s semaforjem vrednost, povečana za vrednost, navedeno v številki parametra.

Primer. Sinhronizacija niti z uporabo semaforjev.

#vključi #vključi ROČAJ hSem; int a; ROČAJ hThr; nepodpisan dolg uThrID; void Thread(void* pParams) ( int i, num = 0; while (1) ( WaitForSingleObject(hSem, INFINITE); for (i=0; i<5; i++) a[i] = num; num++; ReleaseSemaphore(hSem, 1, NULL); } } int main(void) { hSem=CreateSemaphore(NULL, 1, 1, "MySemaphore1"); hThr=CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)Thread,NULL,0,&uThrID); while(1) { WaitForSingleObject(hSem, INFINITE); printf("%d %d %d %d %d\n", a, a, a, a, a); ReleaseSemaphore(hSem, 1, NULL); } return 0; }

Zaščiten dostop do spremenljivk

Obstajajo številne funkcije, ki vam omogočajo delo z globalnimi spremenljivkami iz vseh niti brez skrbi za sinhronizacijo, ker te funkcije ga spremljajo same - njihovo izvajanje je atomsko. Te funkcije so InterlockedIncrement, InterlockedDecrement, InterlockedExchange, InterlockedExchangeAdd in InterlockedCompareExchange. Na primer, funkcija InterlockedIncrement atomsko poveča vrednost 32-bitne spremenljivke za eno, kar je priročno za uporabo za različne števce.

Za pridobitev popolnih informacij o namenu, uporabi in sintaksi vseh funkcij API-ja WIN32 morate uporabiti sistem pomoči MS SDK, vključen v programska okolja Borland Delphi ali CBuilder, kot tudi MSDN, ki je dobavljen kot del programskega sistema Visual C.

Niti so lahko v enem od več stanj:

    pripravljena(pripravljeno) – nahaja se v bazenu niti, ki čakajo na izvedbo;

    tek(izvajanje) - teče na procesorju;

    Čakanje(čakajoče), imenovano tudi idle ali suspended, suspended - v stanju čakanja, ki se konča z nitjo, ki se začne izvajati (Running state) ali preide v stanje pripravljena;

    Prekinjeno (dokončanje) - izvedba vseh ukazov niti je zaključena. Pozneje ga je mogoče izbrisati. Če tok ni izbrisan, ga lahko sistem ponastavi v prvotno stanje za kasnejšo uporabo.

Sinhronizacija niti

Tekoče niti morajo pogosto na nek način komunicirati. Na primer, če več niti poskuša dostopati do nekaterih globalnih podatkov, mora vsaka nit zaščititi podatke pred spremembo druge niti. Včasih mora ena nit vedeti, kdaj bo druga nit dokončala nalogo. Takšna interakcija je obvezna med nitmi istih in različnih procesov.

Sinhronizacija niti ( nit sinhronizacijo) je splošen izraz, ki se nanaša na proces interakcije in medsebojnega povezovanja niti. Upoštevajte, da sinhronizacija niti zahteva, da sam operacijski sistem deluje kot posrednik. Niti ne morejo komunicirati med seboj brez njenega sodelovanja.

V Win32 obstaja več metod za sinhronizacijo niti. Zgodi se, da je v določeni situaciji ena metoda bolj zaželena kot druga. Oglejmo si na hitro te metode.

Kritični odseki

Eden od načinov za sinhronizacijo niti je uporaba kritičnih odsekov. To je edina metoda sinhronizacije niti, ki ne zahteva jedra Windows. (Kritični razdelek ni objekt jedra.) Vendar pa je to metodo mogoče uporabiti samo za sinhronizacijo niti enega samega procesa.

Kritični del je del kode, ki ga lahko izvaja samo ena nit naenkrat. Če je koda, ki se uporablja za inicializacijo matrike, postavljena v kritični razdelek, potem druge niti ne bodo mogle vstopiti v ta del kode, dokler prva nit ne konča z izvajanjem.

Pred uporabo kritičnega odseka ga morate inicializirati s postopkom API Win32 InitializeCriticalSection(), ki je definiran (v Delphiju) na naslednji način:

procedure InitializeCriticalSection(var IpCriticalSection: TRTLCriticalSection); stdcall;

Parameter IpCriticalSection je zapis tipa TRTLCriticalSection, ki se posreduje po sklicu. Natančna definicija vnosa TRTLCriticalSection ni pomembna, saj verjetno ne boste nikoli morali pogledati njegove vsebine. Vse kar morate storiti je, da posredujete neinicializiran vnos parametru IpCtitical Section in ta vnos bo takoj zapolnil postopek.

Ko izpolnite vnos v programu, lahko ustvarite kritični razdelek tako, da del njegovega besedila postavite med klice funkcij EnterCriticalSection() in LeaveCriticalSection(). Ti postopki so opredeljeni na naslednji način:

procedure EnterCriticalSection(var IpCriticalSection: TRTLCriticalSection); stdcall;

procedure LeaveCriticalSection(var IpCriticalSection: TRTLCriticalSection); stdcall;

Parameter IpCriticalSection, ki se posreduje tem proceduram, ni nič drugega kot vnos, ustvarjen s proceduro InitializeCriticalSection().

funkcija EnterCriticalSection preveri, ali kakšna druga nit že izvaja kritični odsek svojega programa, ki je povezan z danim objektom kritičnega odseka. V nasprotnem primeru nit dobi dovoljenje za izvajanje svoje kritične kode oziroma ji to ni preprečeno. Če je tako, se nit, ki zahteva, postavi v stanje čakanja in o zahtevi se naredi zapis. Ker je treba ustvariti zapise, je objekt kritičnega odseka podatkovna struktura.

Ko funkcija Zapusti kritični razdelek kliče nit, ki ima trenutno dovoljenje za izvajanje svojega kritičnega odseka kode, povezanega z danim objektom kritičnega odseka, lahko sistem preveri, ali je v čakalni vrsti še ena nit, ki čaka na sprostitev tega predmeta. Sistem lahko nato odstrani čakajočo nit iz čakajočega stanja in ta bo nadaljevala svoje delo (v časovnih rezinah, ki so ji dodeljene).

Ko končate z delom z zapisom TRTLCriticalSection, ga morate osvoboditi tako, da pokličete proceduro DeleteCriticalSection(), ki je definirana na naslednji način:

procedure DeleteCriticalSection(var IpCriticalSection: TRTLCriticalSection); stdcall;

Včasih je to potrebno pri delu z več nitmi ali procesi sinhronizirati izvajanje dva ali več njih. Razlog za to je najpogosteje, da lahko dve ali več niti zahtevata dostop do skupnega vira, ki res ni mogoče zagotoviti več nitim hkrati. Skupni vir je vir, do katerega lahko hkrati dostopa več izvajajočih se nalog.

Pokliče se mehanizem, ki zagotavlja proces sinhronizacije omejitev dostopa. Potreba po njem se pojavi tudi v primerih, ko ena nit čaka na dogodek, ki ga ustvari druga nit. Seveda mora obstajati način, s katerim bo prva nit prekinjena, preden pride do dogodka. Po tem naj nit nadaljuje z izvajanjem.

Opravilo je lahko v dveh splošnih stanjih. Prvič, naloga lahko izvajati(ali biti pripravljen na izvedbo takoj, ko pridobi dostop do virov CPE). Drugič, naloga je lahko blokiran. V tem primeru je njegovo izvajanje začasno ustavljeno, dokler se vir, ki ga potrebuje, ne sprosti ali pride do določenega dogodka.

Windows ima posebne storitve, ki vam omogočajo, da na določen način omejite dostop do skupnih virov, saj brez pomoči operacijskega sistema ločen proces ali nit ne more sam ugotoviti, ali ima edini dostop do vira. Operacijski sistem Windows vsebuje postopek, ki med eno neprekinjeno operacijo preveri in po možnosti nastavi zastavico za dostop do vira. V jeziku razvijalcev operacijskega sistema se ta operacija imenuje preverjanje in namestitev. Pokličejo se zastavice, ki se uporabljajo za zagotavljanje sinhronizacije in nadzor dostopa do virov semaforji(semafor). Win32 API nudi podporo za semaforje in druge sinhronizacijske objekte. Knjižnica MFC vključuje tudi podporo za te objekte.

Sinhronizacijski objekti in razredi mfc

Vmesnik Win32 podpira štiri vrste sinhronizacijskih objektov - vsi nekako temeljijo na konceptu semaforja.

Prva vrsta objekta je sam semafor, oz klasični (standardni) semafor. Omejenemu številu procesov in niti omogoča dostop do enega vira. V tem primeru je dostop do vira popolnoma omejen (ena in samo ena nit ali proces lahko dostopa do vira v določenem časovnem obdobju) ali pa le majhno število niti in procesov prejme hkratni dostop. Semaforji so implementirani s pomočjo števca, katerega vrednost se zmanjša, ko je semafor dodeljen nalogi, in poveča, ko naloga sprosti semafor.

Druga vrsta sinhronizacijskih objektov je ekskluzivni (mutex) semafor. Zasnovan je tako, da popolnoma omeji dostop do vira, tako da lahko samo en proces ali nit v danem trenutku dostopa do vira. Pravzaprav je to posebna vrsta semaforja.

Tretja vrsta sinhronizacijskih objektov je dogodek, oz predmet dogodka. Uporablja se za blokiranje dostopa do vira, dokler drug proces ali nit ne izjavi, da je vir mogoče uporabiti. Tako ta objekt signalizira dokončanje zahtevanega dogodka.

S sinhronizacijskim objektom četrtega tipa lahko prepoveste izvajanje določenih delov programske kode več nitim hkrati. Da bi to naredili, morajo biti ta območja deklarirana kot kritični odsek. Ko ena nit vstopi v ta razdelek, je drugim nitim prepovedano storiti enako, dokler prva nit ne zapusti razdelka.

Kritični odseki se za razliko od drugih vrst sinhronizacijskih objektov uporabljajo samo za sinhronizacijo niti znotraj istega procesa. Druge vrste objektov se lahko uporabljajo za sinhronizacijo niti znotraj procesa ali za sinhronizacijo procesov.

V MFC je sinhronizacijski mehanizem, ki ga zagotavlja vmesnik Win32, podprt z naslednjimi razredi, ki izhajajo iz razreda CSyncObject:

    CCriticalSection- izvaja kritični del.

    CEvent- implementira objekt dogodka

    CMutex- implementira ekskluzivni semafor.

    CSemaphore- implementira klasični semafor.

Poleg teh razredov MFC definira tudi dva pomožna sinhronizacijska razreda: CSingleLock in CMultiLock. Nadzorujejo dostop do sinhronizacijskega objekta in vsebujejo metode, ki se uporabljajo za zagotavljanje in sprostitev takih objektov. Razred CSingleLock nadzoruje dostop do posameznega sinhronizacijskega objekta in razreda CMultiLock- na več objektov. V nadaljevanju bomo upoštevali le razred CSingleLock.

Ko je kateri koli sinhronizacijski objekt ustvarjen, lahko dostop do njega nadzirate z uporabo razreda CSingleLock. Če želite to narediti, morate najprej ustvariti objekt vrste CSingleLock z uporabo konstruktorja:

CSingleLock(CSyncObject* pObject, BOOL bInitialLock = FALSE);

Prvi parameter posreduje kazalec na sinhronizacijski objekt, na primer semafor. Vrednost drugega parametra določa, ali naj konstruktor poskusi dostopati do tega objekta. Če je ta parameter različen od nič, bo dostop pridobljen, v nasprotnem primeru ne bo poskusov dostopa. Če je dostop odobren, potem nit, ki je ustvarila objekt razreda CSingleLock, bo zaustavljen, dokler metoda ne sprosti ustreznega sinhronizacijskega objekta Odkleni razred CSingleLock.

Ko je ustvarjen objekt tipa CSingleLock, lahko dostop do objekta, na katerega kaže pObject, nadzorujete z dvema funkcijama: Zaklepanje in Odkleni razred CSingleLock.

Metoda Zaklepanje je namenjen pridobitvi dostopa do objekta do sinhronizacijskega objekta. Nit, ki jo je poklicala, je začasno ustavljena, dokler se metoda ne zaključi, to je dokler ni pridobljen dostop do vira. Vrednost parametra določa, kako dolgo bo funkcija čakala na dostop do zahtevanega objekta. Vsakič, ko se metoda uspešno zaključi, se vrednost števca, povezanega s sinhronizacijskim objektom, zmanjša za eno.

Metoda Odkleni Sprosti sinhronizacijski objekt in drugim nitim omogoči uporabo vira. V prvi različici metode se vrednost števca, povezanega s tem objektom, poveča za eno. Pri drugi možnosti prvi parameter določa, koliko naj se ta vrednost poveča. Drugi parameter kaže na spremenljivko, v katero bo zapisana prejšnja vrednost števca.

Pri delu z razredom CSingleLock Splošni postopek za nadzor dostopa do vira je:

    ustvarite objekt tipa CSyncObj (na primer semafor), ki bo uporabljen za nadzor dostopa do vira;

    z uporabo ustvarjenega sinhronizacijskega objekta ustvarite objekt tipa CSingleLock;

    za dostop do vira pokličite metodo Lock;

    dostop do vira;

    Pokličite metodo Unlock, da sprostite vir.

V nadaljevanju je opisano, kako ustvariti in uporabiti semaforje in objekte dogodkov. Ko razumete te koncepte, se lahko preprosto naučite in uporabljate dve drugi vrsti objektov za sinhronizacijo: kritične odseke in mutekse.

Zdravo! Danes bomo še naprej obravnavali značilnosti večnitnega programiranja in govorili o sinhronizaciji niti.

Kaj je "sinhronizacija"? Zunaj področja programiranja se to nanaša na nekakšno nastavitev, ki omogoča, da dve napravi ali programoma delujeta skupaj. Na primer, pametni telefon in računalnik je mogoče sinhronizirati z Google računom, osebni račun na spletnem mestu pa je mogoče sinhronizirati z računi v družbenih omrežjih, da se prijavite z njimi. Sinhronizacija niti ima podoben pomen: je nastavitev medsebojnega delovanja niti. Na prejšnjih predavanjih so naše niti živele in delovale ločeno ena od druge. Eden je nekaj štel, drugi je spal, tretji je nekaj prikazoval na konzoli, a med seboj nista sodelovala. V resničnih programih so takšne situacije redke. Več niti lahko aktivno deluje, na primer, z istim naborom podatkov in v njem nekaj spremeni. To ustvarja težave. Predstavljajte si, da več niti piše besedilo na isto mesto - na primer v besedilno datoteko ali konzolo. Ta datoteka ali konzola v tem primeru postane skupni vir. Niti ne vedo za obstoj druga druge, zato preprosto zapišejo vse, kar lahko uredijo v času, ki jim ga razporejevalnik niti dodeli. V nedavnem predavanju tečaja smo imeli primer, do česa bi to privedlo, spomnimo se: razlog je v tem, da so niti delovale s skupnim virom, konzolo, ne da bi medsebojno usklajevale dejanja. Če je razporejevalnik niti dodelil čas Thread-1, vse takoj zapiše v konzolo. Kaj so druge teme že uspele napisati ali niso imele časa napisati, ni pomembno. Rezultat je, kot vidite, katastrofalen. Zato je bil v večnitnem programiranju uveden poseben koncept mutex (iz angleškega "mutex", "medsebojna izključitev" - "medsebojna izključitev"). Naloga Mutex- zagotoviti mehanizem, tako da ima samo ena nit dostop do objekta ob določenem času. Če je Thread-1 pridobil mutex objekta A, druge niti ne bodo imele dostopa do njega, da bi karkoli spremenile v njem. Dokler se mutex objekta A ne sprosti, bodo preostale niti prisiljene čakati. Primer iz resničnega življenja: predstavljajte si, da se vi in ​​10 drugih neznancev udeležujete usposabljanja. Izmenično morate izražati ideje in o nečem razpravljati. Ker pa se vidita prvič, da se ne prekinjata in ne zdrsneta v hrup, uporabita pravilo »govoreče žoge«: govori lahko samo ena oseba - tista, ki ima žogo v rokah. njegove roke. Tako se razprava izkaže za ustrezno in plodno. Torej, mutex je v bistvu takšna žoga. Če je mutex predmeta v rokah ene niti, druge niti ne bodo mogle dostopati do predmeta. Za ustvarjanje muteksa vam ni treba narediti ničesar: že je vgrajen v razred Object, kar pomeni, da ga ima vsak objekt v Javi.

Kako deluje sinhronizirani operater

Spoznajmo novo ključno besedo - sinhronizirano. Označuje določen del naše kode. Če je blok kode označen s ključno besedo synchronized, to pomeni, da lahko blok izvede samo ena nit naenkrat. Sinhronizacijo lahko izvajamo na različne načine. Ustvarite na primer celotno sinhronizirano metodo: public synchronized void doSomething() ( //...logika metode) Ali pa napišite blok kode, kjer se sinhronizacija izvaja na nekem objektu: javni razred Main ( private Object obj = new Object () ; public void doSomething () ( synchronized (obj) ( ) ) ) Pomen je preprost. Če ena nit vstopi v blok kode, ki je označen z besedo synchronized, takoj pridobi mutex objekta, vse druge niti, ki poskušajo vstopiti v isti blok ali metodo, pa so prisiljene počakati, dokler prejšnja nit ne dokonča svojega dela in sprosti monitor. Mimogrede! Na predavanjih ste že videli primere synchronized , vendar so izgledali drugače: public void swap () ( synchronized (this) ( //...logika metode) ) Tema je nova zate in seveda bo na začetku zmeda s sintakso. Zato se takoj spomnite, da se pozneje ne boste zmedli v metodah pisanja. Ta dva načina pisanja pomenita isto: public void swap () ( sinhronizirano (to) ( //...logika metode) ) public synchronized void swap () ( ) ) V prvem primeru ustvarite sinhronizirani blok kode takoj po vnosu metode. Sinhronizira ga ta objekt, to je trenutni objekt. In v drugem primeru ste besedo sinhronizirali na celotno metodo. Ni več treba eksplicitno navesti nobenega predmeta, na katerem se izvaja sinhronizacija. Ko je celotna metoda označena z besedo, bo ta metoda samodejno sinhronizirana za vse objekte razreda. Ne spuščajmo se v razpravo o tem, katera metoda je boljša. Za zdaj izberite tisto, kar vam je najbolj všeč :) Glavna stvar je, da si zapomnite: metodo lahko razglasite za sinhronizirano le, če vso logiko v njej izvaja ena nit hkrati. Na primer, v tem primeru bi bila napaka, če bi metodo doSomething() sinhronizirali: public class Main ( private Object obj = new Object () ; public void doSomething () ( //... nekaj logike, ki je na voljo vsem nitim sinhronizirano (obj) ( //logika, ki je na voljo samo eni niti hkrati) ) ) Kot lahko vidite, del metode vsebuje logiko, za katero sinhronizacija ni potrebna. Kodo v njem lahko izvaja več niti hkrati, vsa kritična mesta pa so dodeljena ločenemu sinhroniziranemu bloku. In trenutek. Poglejmo pod mikroskop naš primer zamenjave imen iz predavanja: public void swap () ( sinhronizirano (to) ( //...logika metode } } Bodite pozorni: sinhronizacija se izvaja s tem. To je na določenem objektu MyClass. Predstavljajte si, da imamo 2 niti (Thread-1 in Thread-2) in samo en objekt MyClass myClass. V tem primeru, če Thread-1 pokliče metodo myClass.swap(), bo mutex objekta zaseden, Thread-2 pa bo ob poskusu klica myClass.swap() visel in čakal, da se mutex sprosti. Če imamo 2 niti in 2 objekta MyClass - myClass1 in myClass2 - na različnih objektih, lahko naše niti preprosto hkrati izvajajo sinhronizirane metode. Prva nit se izvede: myClass1. zamenjaj(); Drugi: myClass2. zamenjaj(); V tem primeru ključna beseda synchronized znotraj metode swap() ne bo vplivala na delovanje programa, saj se sinhronizacija izvaja na določenem objektu. In v slednjem primeru imamo 2 objekta.Zato niti drug drugemu ne povzročajo težav. Konec koncev dva objekta imata 2 različna muteksa in njuna pridobitev je neodvisna drug od drugega.

Značilnosti sinhronizacije v statičnih metodah

Kaj storiti, če morate sinhronizirati statična metoda? class MyClass ( private static String name1 = "Olya" ; private static String name2 = "Lena" ; public static synchronized void swap () ( String s = name1; name1 = name2; name2 = s; ) ) Ni jasno, kaj bo v tem primeru izpolni vlogo mutex. Navsezadnje smo se že odločili, da ima vsak objekt mutex. Toda težava je v tem, da za klic statične metode MyClass.swap() ne potrebujemo objektov: metoda je statična! Torej, kaj je naslednje? :/ Pravzaprav s tem ni problema. Ustvarjalci Jave so poskrbeli za vse :) Če je metoda, ki vsebuje kritično “večnitno” logiko, statična, bo sinhronizacija izvedena po razredih. Za večjo jasnost lahko zgornjo kodo prepišemo kot: class MyClass ( private static String name1 = "Olya" ; private static String name2 = "Lena" ; public static void swap () ( synchronized (MyClass. class ) ( String s = ime1 ; ime1 = ime2; ime2 = s; ) ) ) Načeloma bi se tega lahko zamislili sami: ker ni objektov, mora biti sinhronizacijski mehanizem nekako "vpet" v same razrede. Tako je: sinhronizirate lahko tudi med razredi.