Know-how, prelegere, fluxuri

Monitoare și semaphore. Din filosofii de meserie.

Blocarea secțiunilor critice vă permite să faceți față problemelor de rasă. Ca instrument de blocare, am analizat instrucțiunea de blocare C #. Dar Cadrul. Netul include mai multe clase care vă permit să organizați o blocare. Abundența mijloacelor diferite de a rezolva o problemă indică deseori că nu există niciun instrument universal adecvat pentru toate cazurile de viață.







Instrucțiunea de blocare este de fapt un add-in peste clasa Monitor. înregistrare:

poate fi considerată ca o scurtă formă a următoarei intrări:

Metoda statică Enter din clasa Monitor închide secțiunea critică imersată în blocul try cu obiectul de blocare. Semantica este aceeași ca și pentru blocarea operatorului. Toate celelalte fire care încearcă să intre în secțiunea critică, blocate cu cheia dulapului. va aștepta ca secțiunea să fie deschisă. La orice capăt al blocului de încercare, blocul final este executat. care elimină blocarea.

Clasa Monitor are alte metode care vă permit să organizați o blocare. Unele fluxuri, ca niște oameni, urăsc mult timp în coada de așteptare. În astfel de situații, aceștia preferă să renunțe complet la această activitate sau să încerce să se întoarcă altă dată, când, probabil, nu va mai exista o coadă. La aceste fire, clasa Monitor oferă metoda TryEnter. care are următoarea sintaxă:

Clasa Monitor are trei metode mai importante - așteptați. Puls. PulseAll. Aceste metode sunt numite în secțiunea critică. Acestea sunt interconectate și permit două sau mai multe fire să-și sincronizeze activitatea prin trimiterea de notificări reciproc.

Permiteți, atunci când efectuați acțiuni în secțiunea critică, firul să detecteze că un alt fir trebuie să efectueze o anumită prelucrare a resursei închise. În această situație, firul trebuie să-și suspende activitatea, să elibereze resursele temporare pentru a permite unui alt fir să efectueze procesarea necesară și apoi să notifice firul suspendat că poate continua să funcționeze. Metodele Wait-Pulse permit implementarea scenariului descris. Metoda Wait vă permite să suspendați un fir dintr-o secțiune critică. Metoda Pulse trimite o notificare care permite thread-ului suspendat să continue executarea. Metoda PulseAll vă permite să continuați executarea tuturor firelor suspendate.

Metoda Wait este supraîncărcată și vom lua în considerare doar varianta principală, care are următoarea sintaxă:

Parametrul obj specifică obiectul de sincronizare care închide secțiunea critică. De obicei, metoda nu utilizează valoarea returnată și este numită ca operator, nu ca o funcție. Semantica exactă a metodei este după cum urmează. Metoda eliberează obiectul de sincronizare, firul întrerupe lucrarea și devine o coadă specială care așteaptă notificarea. Atunci când o altă metodă care captează obiectul de sincronizare eliberat execută metoda Pulse. atunci prima metodă din coada de așteptare este transferată în coada de fire gata pentru a rula. Când este timpul să executați metoda, atunci starea sa este restabilită, toate resturile făcute anterior sunt restaurate și metoda continuă să funcționeze.

Metoda Pulse are următoarea sintaxă:

Când metoda este executată, coada de așteptare a firului este notificată cu obiectul sincronizare obj. Drept urmare, firul din această coadă intră în stare gata. Când firul numit metoda Pulse. eliberează încuietoarea, apoi firul terminat continuă execuția.

Colaborarea metodelor Wait-Pulse permite rezolvarea multor probleme de interacțiune a fluxurilor care nu pot fi rezolvate în alte moduri.

Înainte de a da un exemplu care demonstrează interacțiunea thread-urilor, voi vorbi din nou despre cota dificilă a programatorilor care creează programe paralele. Un exemplu de aplicare a metodelor Wait and Pulse din documentația oficială funcționează corect, cu condiția ca cele două fire care rulează să funcționeze mai întâi una dintre ele, altfel apare o aplecare și aplicația se blochează. Când am început să lucrez la un proiect alternativ, trebuia să mă duc și înainte de a scăpa de situația constantă care a apărut.

Exemplu de cooperare între două fire utilizând schema de așteptare-impuls

Să considerăm cooperarea a două fluxuri pe un exemplu de următoarea sarcină. Fie ca firele să proceseze un set de date. Primul thread gestionează prelucrarea principală a acestor date, cu condiția ca fiecare element al acestor date să îndeplinească anumite cerințe. Dacă această condiție nu este îndeplinită, firul își suspendă activitatea, permițând unui alt fir să aducă elementul de date în starea dorită, după care primul fir poate continua lucrul.

Pentru simplitate, vom presupune că setul de date prelucrate este o serie de numere întregi. Procesarea în sine este redusă la găsirea elementului maxim. În acest caz, elementele matricei trebuie să satisfacă condiția că fiecare dintre ele nu este mai mică de 100. Al doilea flux înlocuiește elemente "mici" cu valori mai mari de 100.

Aici este clasa care efectuează procesarea matricelor necesare. Să începem cu o descriere generală a clasei:

Ambele fire vor lucra pe memoria partajată a obiectelor din această clasă. Resursa variabilă este matricea procesată. Alte variabile sunt necesare pentru schimbul de informații între firele care interacționează. Constructorul de clase și metodele de proprietate efectuează munca tipică pentru aceștia. Metoda Init vă permite să creați matricea originală care trebuie procesată.

Acum adăugăm clasei o metodă care va fi executată în primul fir, calculând elementul maxim și suspendându-și activitatea atunci când următorul element este mai mic de 100:







Acordați atenție pachetului Monitor.Pulse și Monitor.Așteptați. - vom notifica celălalt fir că poate intra în starea gata și poate intra în modul de așteptare. Iată codul pentru un alt fir care fixează elementele "incorecte":

Dacă metoda începe mai întâi, aceasta intră în modul de așteptare. Altfel, corectează elementul și execută o grămadă de metode Pulse-Wait. Reluarea lucrului are loc prin verificarea condiției de finalizare a procesării matricei. Dacă primul fir este terminat, cel de-al doilea fir se va termina. Vă recomandăm să studiați cu atenție structura interacțiunii fluxurilor, deoarece nu este la fel de simplă cum pare la prima vedere.

Iată rezultatele unei sesiuni:


click pentru a mari imaginea
Fig. 5.6. Rezultatele muncii cooperative a celor două fluxuri

O altă modalitate de a bloca este furnizarea de semaphore. Semaphorele au două caracteristici importante:

  • Semaphorele sunt mai stricte decât metodele convenționale de blocare. Ele nu au un obiect de sincronizare și nu permit nici un flux în secțiunea critică când este ocupat.
  • Semaphorele sunt mai moi decât metodele convenționale de blocare. Acestea vă permit să introduceți secțiunea critică a mai multor fire.

Semaphorele permit modelarea situației de admitere a clienților într-o zonă protejată cu un număr limitat de locuri. Exemplele includ locuri precum o parcare păzită, un restaurant, o mașină de dormit. Dacă toate locurile de parcare sunt ocupate, se creează o coadă și puteți intra în parcare numai după ce părăsiți mașina din parcare.

Semaphorele sunt adesea folosite în organizarea lucrului cu serverul, permițându-vă să reduceți sarcina pe server, limitând numărul de clienți care lucrează simultan cu serverul.

Clasa SemaphoreSlim specifică implementarea semaphorelor. Clasa are doi constructori. Primul constructor are un parametru reprezentând numărul inițial de fire care sunt permise să treacă prin semafor. În timpul funcționării, sistemul de operare poate crește numărul de fire permise. În al doilea constructor, al doilea parametru vă permite să specificați numărul maxim de fire care nu pot fi depășite. Adesea semaphorele sunt folosite în modul de a permite doar un fir.

Clasa SemaphoreSlim are două metode principale - Wait and Release. Metodele sunt supraîncărcate, însă ne limităm la descrierea unei implementări a acestor metode.

Lăsați semaforul aici. permițând clienților n. Apoi, de fiecare dată când se efectuează apelul sem.Wait (), numărul de clienți permiși să se conecteze va fi decrementat cu unul. De îndată ce acest număr devine zero, se creează o coadă de client.

Metoda de deblocare efectuează operația inversă, mărind numărul de clienți permiși până la atingerea valorii maxime.

Demonstrăm lucrarea semaforilor pe exemplul problemei clasice a programării paralele - problema "filozofilor de mese".

Diner Filozofi

Problema "filozofilor de mese", propusă de E. Dijkstroy, demonstrează bine problemele apărute în procesele paralele și, în special, problema binecunoscută a clinching-ului. Omiterea unor detalii artistice ale problemei, o voi da în formula următoare.

Cinci filozofi sunt invitați la cină. S-au așezat la o masă rotundă, în fața fiecăruia se găsea o farfurie de spaghete, în partea dreaptă a antenei se află o furculiță. Așa cum este adevărat cu adevărații filosofi, oaspeții nu mănâncă atât de mult, încât se dedau la reflecții. Când un filosof își amintește de mâncare, își ia furculita, dar descoperă curând că este imposibil să mănânci spaghete cu o singură furculiță. Dacă fișa vecinului este liberă, atunci el ia furculița vecinului și mănâncă, până când se plimba din nou în reflecții, eliberând ambele furci. Dacă furca vecinului este ocupată, filosoful se așteaptă la eliberarea ei, fără a-și elibera furculița.

Să analizăm această problemă din punctul de vedere al programării paralele. Cina fiecărui filosof este un proces separat, care include trei etape - gândirea, așteptarea și mâncarea. Toate procesele au loc în paralel. Resursele comune utilizate de procese sunt furci. Fiecare proces are nevoie de două resurse. Fiecare dintre ei împărtășește cu procesul vecin. În timpul prânzului, poate apărea o intervenție chirurgicală. Dacă toți filozofii doresc în același timp să treacă la alimente, luând fiecare furculiță, toți vor intra într-o stare de așteptare. Fiind adevărați filozofi, nu intră niciodată în capul lor să elibereze furculita, oferind ocazia de a fi mulțumiți de vecinul lor.

Care este dificultatea implementării unei astfel de sarcini. Toți filosofii folosesc același mod de a mânca - aceeași metodă. Diferența este numai în resursele utilizate - furculițele transmise ca parametri la metodă. Când începe o masă, pentru un succes garantat, filozoful ar trebui să blocheze ambele resurse de care are nevoie. Cu toate acestea, metodele de blocare propuse nu vă permit să blocați resursele, blochează secțiunea critică, un cod care utilizează resurse. Desigur, nu este dificil să blochezi codul complet, blocând astfel toate furcile. Dar aceasta este o decizie proastă, pentru că în acel moment un singur filozof poate mânca spaghete, în timp ce, fără a încălca regulile, există doi filozofi în același timp, folosind 4 din cele 5 furci (în general, filosofii N / 2 pot mânca prânzul împreună).

Cum să evităm o strângere, fără a bloca posibilitatea prânzului tuturor celorlalți filosofi? O posibilă soluție este aceea de a atașa filosofilor un slujitor care este obligat să se asigure că toți filozofii nu pot începe să mănânce simultan. Dacă cel puțin o priză este liberă, atunci aceasta asigură absența unei îmbinări.

În decizia noastră, care garantează, de asemenea, absența unei soluții, este utilizată o altă strategie - o strategie de reglementare a utilizării resurselor. Filozofii sunt ușor instruiți. Să învățăm chiar pe filozofi (filozofi cu număr egal) să ia prima dată pe furcă pe dreapta, iar filozofii ciudați iau mai întâi furculița din stânga. Această soluție evită slăbirea. Într-adevăr, luați în considerare doi filosofi vecini care, în același timp, au decis să mănânce. Cel din stânga ia luat furca stângă, cea dreaptă - cea dreaptă. Furca între ele este liberă și va fi dată unuiuia dintre filosofi (cel care reacționează mai repede). Unul dintre acești doi filozofi vor mânca, vecinii săi așteaptă eliberarea furcilor. Nu apare situația în care se află.

O astfel de decizie este adesea folosită în practică. În orașele în care se produc adesea blocaje, mașinile cu numere impare au permisiunea de a conduce autoturisme cu numere impare chiar și în zile, chiar și în zile chiar.

Să ne îndreptăm acum spre construirea unui model de program pentru problema noastră. Mai întâi voi da o descriere generală a clasei care modelează cina filosofilor:

Coarnele de furcă care reprezintă resursele sunt reprezentate ca o serie de obiecte din clasa SemaphoreSlim. Fiecare fișă este un fel de semafor, calculat, notat, numai pentru un singur client. Dacă este ridicată, atunci un alt filozof, care va susține această furculiță, va trebui să aștepte. Apetitul filozofilor este limitat, - repetarea variabilă arată câte filozofi pot începe să mănânce pentru a se satura. Gama de state va păstra istoria statelor în care filozofii au fost în cursul cina.

Acum adăugăm la clasa noastră o metodă care inițializează procesele paralele de prânz pentru fiecare dintre filozofi:

Pentru fiecare filozof creează propriul flux. Toate firele sunt pornite pentru execuția paralelă. Toate firele execută aceeași metodă DinnerForFhilosophers. Când executați metoda, este trecut parametrul care specifică numărul filozofului.

Metoda DinnerForFilozofi descrie procesul de prânz propriu-zis. Iată codul său:

În primul rând, numărul filozofului determină resursele de care are nevoie. Apoi resursele sunt sortate. Apoi, într-un ciclu se îndeplinesc trei etape - să gândim, să așteptăm și să mâncăm. Când filozoful primește prima furcă (left_fork.Wait), intră în starea de așteptare, iar mufa devine inaccesibilă altor filosofi. Atunci când filosoful ia cea de-a doua furculiță (right_fork.Wait), începe să mănânce și fișa devine inaccesibilă altor filosofi. La sfârșitul mesei, ambele furci sunt eliberate. Datorită regulii de introducere a furcii de comandă introduse, este posibil să se evite o strângere.

Luați în considerare acum codul proiectului consola cu procedura Main. a modelat rolul gazdei organizând cina filosofilor:

La proprietarul lacom, care ia puțin timp la cină, nu toți filozofii au timp să mănânce măcar ceva, să nu mai spun că trebuie să mănânc:


Fig. 5.8. Filozofii foame







Articole similare

Trimiteți-le prietenilor: