Sunteți pe pagina 1din 22

Cursul 7:

Sincronizarea
threadurilor
Cursul 6: Recapitulare
• Concurenta: multiprocesare, multitasking, sau orice alta
combinatie
• Conditie de cursa: situatia in care rezultatul unei
executii concurente este dependent de intercalarea
nedeterminista
• Instructiune atomica: executia sa nu poate fi
intercalata de alte instructiuni inainte de a se finaliza
• Sincronizarea – un mecanism cu ajutorul caruia
procesele comunica intre ele pentru a determina
actiunile ce vor avea loc
• Cerintele sincronizarii– ex: filozofii care cineaza
• Asigurarea excluziunii mutuale
• Evitarea blocajelor
• Evitarea infometarii
• Sectiunea critica - secventa de cod ce poate fi
executata doar de catre un singur thread la un moment
dat [celelalte threaduri vor putea rula doar secvente non-
critice in paralel]
Cursul 6: Recapitulare
Cursul 6: Recapitulare
Ex: Incercari de rezolvarea a
problemei laptelui in exces
Cursul 7: Sincronizarea threadurilor
• Solutia Peterson
• Locks (lacate)
• Lacate Hardware
• Solutii la nivel mai inalt
• Semafoare
• Monitoare si variabile de stare
Solutia Peterson
Solutia Peterson

Peterson:
Solutia Peterson- demonstratie
! Algoritmul lui Peterson combina ideile tentativelor II si III discutate anterior; daca
ambele procese si-au setat flag-ul de intrare pe valoare true, atunci variabila turn
determina cine va intra in sectiunea critica
! Algoritmul lui Peterson satisface cerinta de excluziune mutuala, asa cum se poate
vedea mai jos:
! Sa presupunem ca atat P1 cat si P2 se afla in sectiunea critica si ca P1 a intrat inainte de P2
! Atunci cand P1 este in sectiunea critica avem enter1 =true, in consecinta P2 trebuie sa
gaseasca turn = 2 ca sa poata intra si el in sectiunea critica
! P2 executa instructiunea 2 dupa ce P1 a intrat in sectiunea critica: imposibil, turn = 1 si P2
nu poate intra in sectiunea critica de vreme ce P1 nu schimba valoarea variabilei turn cand
este in sectiunea critica
! P2 a executat linia 2 inainte ca P1 sa intre in sectiunea critica: imposibil pentru ca atunci P1
ar fi vazut enter2 = true si turn = 1; insa P2 ar trebui sa vada turn = 2 ca sa intre in sectiunea
critica avand in vedere ca enter1 = true
Solutia Peterson- demonstratie
! Algoritmul lui Peterson evita infometarea:
! Sa presupunem ca P1 este fortat sa astepte in protocolul de intrare in sectiunea
critica pe perioada nedeterminata. In acest caz P2 poate realiza una dintre
urmatoarele 3 actiuni:
1. Sa se gaseasca in sectiunea non-critica: atunci variabila enter2 = false ->
permite P1 sa intre in sectiunea critica.
2. Asteapta pe perioada nedeterminata in protocolul de intrare in sectiunea
cristica: imposibil pentru ca turn nu poate fi egal cu 1 si 2 in acelasi timp
3. Cicleaza in mod repetat prin cod: atunci la un moment dat P2 va seta turn =
1 si nu va mai schimba valoarea variabilei inapoi
Solutia Peterson- concluzii

! Solutia Peterson functioneaza, totusi nu este satisfacatoare


" Foarte complexa– chiar si pentru un exemplu extrem de simplu
4 Este greu sa convingi ca functioneaza cu adevarat
" In timp ce P1/P2 asteapta, consuma timp CPU
4 Acest lucru mai este numit si asteptare activa - “busy-waiting”
4 Asimetrica
! Exista o alternativa mai buna
" Implementarea in harware-ul a unor primitive mai bune decat cele de tip
“load & store”
" Se pot construi noi nivele de abstractizare folosindu-se acest suport
hardware.
Solutii pentru sincronizare
! Vom implementa diverse primitive de sincronizare la nivel inalt, folosind
operatii atomice
" Este problematic daca sunt disponibile doar primitive atomice de tipul
“load & store”
" Este necesara construirea unor primitive folositoare la nivel-utilizator
Locks (Lacate)
Locks
! Sa presupunem ca avem o implementare a unui lacat, acesta va avea
urmatoarele metode:
" Lock.Acquire() – asteapta pana cand lacatul este liber, apoi lacatul
este preluat
" Lock.Release() – deblocare, “trezirea” tuturor threadurilor care
asteapta
" Operatiile trebuie sa fie atomice – daca doua threaduri asteapta un
lacat si ambele observa ca acesta este liber, doar un thread va reusi sa
preia lacatul
! Problema “pre mult lapte” se usureaza:
milklock.Acquire();
if (nomilk)
buy milk;
milklock.Release();
! Reamintim ca segmentul de cod dintre Acquire() si Release() este
“Sectiunea critica”
Activarea/dezactivarea intreruperilor
! Cum putem construi operatii atomice cu instructiuni multiple?
" Reamintim: dispatcher-ul primeste controlul in doua modalitati.
4 Interna: Thread-ul elibereaza CPU
4 Externa: Intreruperile fac ca dispatcher-ul sa preia CPU
" Pe sistem uniprocesor schimbarile de context pot fi evitate prin:
4 Evitarea evenimentelor interne care cedeaza controlul procesorului
4 Prevenirea evenimentelor externe prin dezactivarea intreruperilor
! In consecinta, o implementare simplista unui lacat ar putea arata asa:
LockAcquire { disable Ints; }
LockRelease { enable Ints; }
! Probleme in acest model:
" Utilizatorul nu ar trebui sa faca asta !
LockAcquire();
While(TRUE) {;}
" Sisteme in timp real – nu exista garantii ale raspunsului in timp real
4 Sectiunile critice pot fi extinse in mod arbitrar
" Ce se intampla cu I/O sau alte evenimente critice din sistem?
4 “Reactorul este aproape de punctul de topire. Help?”
Implementari mai bune ale lacatelor prin
dezactivarea intreruperilor
• Ideea de baza: gestionarea unei variabile de tip “lacat” si
impunerea excluziunii mutuale in timpul operatiilor cu acea
variabila

int value = FREE;

Acquire() { Release() {
disable interrupts; disable interrupts;
if (value == BUSY) { if (anyone on wait queue) {
put thread on wait queue; take thread off wait queue
Go to sleep(); Place on ready queue;
// Enable interrupts? } else {
} else { value = FREE;
}
value = BUSY; enable interrupts;
} }
enable interrupts;
}
Implementari mai bune ale lacatelor
prin dezactivarea intreruperilor
• Nota: Spre
deosebire de int value = FREE;
celelalte solutii,
sectiunea critica Acquire() {
(din Acquire()) disable interrupts;
este foarte scurta if (value == BUSY) {
• Utilizatorul lacatului put thread on wait queue;
poate sa stea cat enable interrupts;
doreste in Go to sleep();
sectiunea critica:
nu afecteaza } else {
comportamentul value = BUSY;
per ansamblu }
• Intreruperi critice enable interrupts;
sunt preluate la }
timp!
Concluzii legate de dezactivarea
intreruperilor
• Probleme cu solutia precedenta:
• Implementarea lacatelor nu poate fi lasata utilizatorilor
• Nu functioneaza bine pe sisteme multiprocesor
• Dezactivarea intreruperilor pe toate procesoarele necesita
un schimb de mesaje, ceea ce inseamna un consum mare
de timp
• Alternativa: secvente de instructiuni atomice
• Aceste instructiuni citesc o valoare din memorie si scriu o
noua valoare in mod atomic
• Hardware-ul este responsabil pentru implementarea
corecta a acestora
• Pe un singur procesor (nu este greu)
• Pe mai multe procesoare (necesita ajutorul unui protocol
de coerenta a cache-ului )
• Spre deosebire de dezactivarea intreruperilor, secventele
de instructiuni atomice pot fi folosite atat pe sisteme
uniprocesor cat si pe sisteme multiprocesor
Lacate hardware
Secvente atomice de tipul Citire-Modificare-Scriere
Secvente atomice de tipul Citire-
Modificare-Scriere
• test&set (&address) { /*
most architectures */
result = M[address];
M[address] = 1;
return result;
}
• swap (&address, register) { /*
x86 */
temp = M[address];
M[address] = register;
register = temp;
}
Secvente atomice de tipul Citire-
Modificare-Scriere
• test&set (&address) { /* most architectures
*/
result = M[address];
M[address] = 1;
return result;
}
int value = 0; // Free
Acquire() {
while (test&set(value)); // while busy
}
//Critical section
Release() {
value = 0;
}
• Explicatie:
• Daca lacatul este liber, test&set citeste 0 si seteaza value=1, preluand
lacatul. Intoarce 0 la iesire
• Daca lacatul este ocupat, test&set citeste 1 si seteaza value=1 (nici o
modificare). Returneaza 1, asa ca bucla continua
• La resetarea lui value = 0, altcineva poate prelua lacatul
• Busy-Waiting: threadul consuma cicluri de procesor in timp ce asteapta
Secvente atomice de tipul Citire-
Modificare-Scriere
• Pro
• Masina poate accepta intreruperi
• Codul utilizatorului poate folosi lacatul oferit de sistem
• Functioneaza pe sisteme multiprocesor
• Contra
• Este ineficient pentru ca thread-urile vor consuma cicluri
in “busy-waiting”
• Thread-urile care asteapta pot lua cicluri de la threadul
care blocheaza lacatul (nimeni nu castiga!)
• Inversiune de prioritate: Daca thread-ul “busy-waiting”
are o prioritate mai mare ca threadul care tine lacatul Þ
nu exista progres!
• Problema inversiunii de prioritate s-a
manifestat in cazul primului Rover Martian
Sfarsitul cursului 7

S-ar putea să vă placă și