Sunteți pe pagina 1din 26

Cursul 8:

Sincronizarea threadurilor
(continuare)
Cursul 7: Recapitulare
Solutii de sincronizare
Hardware
Operatii load/store
load: incarca date din memorie, prin copiere din memorie in registri
store: salveaza date in memorie, prin copiere din registri in memorie

Dezactivarea intreruperilor
Test&set
API de nivel inalt
Lacate
Semafoare
Monitoare
Send/Receive
Cursul 7: Recapitulare
Locks (lacate)
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”
Cursul 7: Recapitulare
Dezactivarea intreruperilor
■ Implementare simplista a lacatelor:
LockAcquire { disable Ints; }
LockRelease { enable Ints; }

■ Implementare mai buna a lacatelor:


int value = FREE;
Release() {
Acquire() { disable interrupts;
disable interrupts;
if (anyone on wait queue) {
if (value == BUSY) {
take thread off wait queue
put thread on wait queue;
Place on ready queue;
Go to sleep();
} else {
// Enable interrupts?
} else {
value = FREE;
value = BUSY;
}
} enable interrupts;
enable interrupts; }
}
Cursul 7: Recapitulare
Suport pt sincronizare oferit prin setul de instructiuni al procesorul -
Test&Set

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;
}
Cursul 8: Cuprins
• Lacate (Cursul 7)
• Solutii de nivel inalt
• Semafoare
• Monitoare si variabile de conditie
Solutii de nivel inalt in sincronizarea
proceselor
Semafoare
Monitoare si variabile de conditie
Solutii de nivel inalt - semafoarele
Semafoarele
• Semafoarele pot fi considerate un model de lacat generalizat
• Au fost introduse pentru prima oara de Dijkstra in anii 60
• Au fost principalele primitive de sincronizare din sistemul de operare
UNIX
• Definitie: Un semafor are o valoare intreaga ne-negativa si
implementeaza urmatoarele doua operatii:
• P(): o operatie atomica ce asteapta ca semaforul sa devina mai mare
ca 0, ca mai apoi sa decrementeze valoarea semaforului cu 1
• Este asemanatoare cu operatia wait()
• V(): o operatie atomica ce incrementeaza semaforul cu 1 si care
trezestete o entitate blocata in operatia P(), daca exista vreuna
• Este asemanatoare cu operatia signal()
• !Observatie: P() face referire la “proberen” (a testa) si V() face referire
la “verhogen” (a incrementa) din olandeza
Semafoarele
• Semafoarele pot fi comparate cu numerele intregi, cu
urmatoarele diferente:
• Nu au valori negative
• Singurele operatii permise sunt P si V – nu pot citi sau scrie o
valoare cu exceptia initializarii
• Operatiile trebuie sa fie atomice
• Doua P() –uri succesive nu pot decrementa valoarea mai jos de 0
• In mod similar, thread-urile care intra in sleep mode in P() vor fi
reactivate prin V() – chiar daca acest lucru are loc concomitent
• Poate fi facuta o analogie cu semafoarele cailor ferate
TMM – implementare cu
semafoare
Thread A Thread B
Lock. Aquire() Lock. Aquire()
if (noMilk){ if (noMilk){
buyMilk(); buyMilk();
} }
Lock.Release() Lock.Release()

//implementare cu semafoare
S=1
Semaphore. P() //S.Wait() Semaphore. P() //S.Wait()
if (noMilk){ if (noMilk){
buyMilk(); buyMilk();
} }
Semaphore.V() //S.Signal() Semaphore.V() //S.Signal()
Utilizarile semafoarelor
• Excludere mutuala (valoarea initiala = 1)
• Denumit si “Semafor binar”.
• Poate fi folosit pentru excludere mutuala:
semaphore.P();
// Sectiune critica aici
semaphore.V();
• Constrangeri de planificare (valoarea initiala = 0)
• Lacatele se descurca bine in problema excluderii mutuale, dar ce se
intampla atunci cand doresti ca un thread sa astepte un eveniment?
• Exemplu: sa presupunem ca trebuie implementat ThreadJoin() care
trebuie sa astepte terminarea unui alt thread:
Initial value of semaphore = 0
ThreadJoin {
semaphore.P();
}
ThreadFinish {
semaphore.V();
}
• Shared pool de resurse (valoare initiala n):
• ex server Web cu un listener si un pool de threaduri de tip workers (n)
Exemplul 1 de lucru cu semafoare
S=2
Thread 1 Thread 2
S.P() S.P()
S.P() S.V()
S.V()
S.V()
Valoare Coada stare T1 stare T2
T Operatie 2 goala executie executie
T1 S.P()
T2 S.P()
T1 S.P()
T2 S.V()
T1 S.V()
T1 S.V()
Exemplul 1 de lucru cu semafoare
S=2
Thread 1 Thread 2
S.P() S.P()
S.P() S.V()
S.V()
S.V()
Valoare Coada stare T1 stare T2
T Operatie 2 goala executie executie
T1 S.P() 1 goala executie executie
T2 S.P() 0 goala executie executie
T1 S.P() 0 T1 blocat executie
T2 S.V() 1 T1 executie executie
T1 S.V() 0 goala executie executie
T1 S.V() 1 goala executie executie
2 goala executie executie
Exemplul 2 de lucru cu semafoare
S1=0; S2=0
Thread A Thread B
buyMilk() buyCake()
S1.V() S2.V()
S2.P() S1.P()
eat() eat()
Problema producator-consumator cu buffer
comun
• Definirea problemei
• Producatorul introduce elemente intr-un buffer comun
• Consumatorul le foloseste
• Exista nevoia de sincronizare pentru a coordona producatorul si
consumatorul
• Pentru a se evita activitati succesive P/C, este necesara crearea
unui buffer cu o marime fixa intre P si C
• Accesul la acest buffer trebuie sincronizat
• Producatorul trebuie sa astepte daca bufferul este plin
• Consumatorul trebuie sa astepte daca buffer-ul este gol
• Exemplu: Automatul de cola
• Producatorul poate pune un numar limitat de cola in automat
• Consumatorul nu poate consuma cola daca automatul este gol

Producer Buffer Consumer


Restrictii pentru solutie
• Constrangeri de corectitudine:
• Consumatorul trebuie sa astepte ca producatorul sa adauge ceva in
buffer, daca nu exista elemente in buffer
• Producatorul trebuie sa astepte golirea bufferului (scoaterea a cel
putin un element), daca este plin
• Un singur thread pe rand poate manipula coada bufferului
(excluziune mutuala)
• Sa ne reamintim de ce este necesara excluziunea mutuala!
• Pentru ca computerele sunt “proaste” J
• Sa ne imaginam urmatoarea situatie in viata reala: angajatul umple
automatul si in acelasi timp o persoana incearca sa introduca bani
in automat
• Regula:
Sa se foloseasca un semafor diferit pentru fiecare restrictie:
• Semaphore fullBuffers; //restrictia consumatorului
• Semaphore emptyBuffers;//restrictia producatorului
• Semaphore mutex; //excluziune mutuala
Solutia problema P/C
Semaphore fullBuffer = 0;
Semaphore emptyBuffers = numBuffers;
Exemplu
Semaphore mutex = 1;
Succesiune empty full
Producer(item) { Initial 4 0
emptyBuffers.P();
mutex.P(); Producer() 3
Enqueue(item); emptyBuffers.P()
fullBuffers.V()
1
mutex.V();
fullBuffers.V(); Producer() 2
}
emptyBuffers.P()
fullBuffers.V()
2
Consumer() { Producer() 1
fullBuffers.P();
mutex.P();
emptyBuffers.P()
fullBuffers.V()
3
item = Dequeue();
mutex.V(); Consumer( 2
emptyBuffers.V(); ) 2
return item; fullBuffers.V()
} emptyBuffers.P()
Solutii de nivel inalt - monitoare si variabile de
conditie
Necesitatea monitoarelor si a variabilelor
de conditie
• Semafoarele sunt un pas urias inainte; sa incercam sa ne imaginam
cum ar arata o solutie la problema P/C doar cu ajutorul operatiilor de
tip load/store
• Problema este ca semafoarele au un rol dublu:
• Sunt folosite atat ca mutex cat si pentru restrictii de planificare

• Ideea mai clara: Pentru excluziune mutuala se foloseste un lacat si


pentru restrictii de planificare se folosesc variabile de conditie
• Definitie: Monitor - un lacat si 0 sau mai multe variabile de conditie
pentru a administra accesul concurent la date partajate
• Anumite limbaje de programare, cum ar fi Java, ofera aceste structuri in
mod nativ
• Majoritatea in schimb folosesc lacate si variabile de conditie ne-native
Necesitatea monitoarelor si a
variabilelor de conditie
• lacat: lacatul confera excluziune mutuala la datele
partajate
• Intotdeauna se preia inainte de a accesa o structura de date
partajate
• Intotdeauna se elibereaza dupa ce se termina utilizarea
datele partajate
• Lacatul este la inceput liber
• variabilele de conditie: o coada de threaduri ce
asteapta in interiorul sectiunii critice
• Ideea principala: un thread poate intra in “sleep” in
sectiunea critica si va elibera lacatul intr-o singura operatie
atomica
• Contrast cu semafoarele:
• nu se asteapta in interiorul sectiunilor critice (intra in sleep si
elibereaza lacatul)
• nu au stare!!! (de ex. Signal() nu are nici un efect daca nu exista
procese care sa astepte – Wait())
Operatii ale variabilelor de
conditie
• Wait(Lock lock): Atomic(release, go to sleep); cand
procesul se trezeste va trebui sa recastige lacatul
• Signal(): trezeste threadul care astepata, daca
exista. Altfel nu face nimic (spre deosebire de
Semafoare unde se incrementa valoarea
semaforului)
• Broadcast(): trezeste toate threadurile care
asteapta

• Obs: Pentru a utiliza variabile de conditie threadul


trebuie sa detina mai intai lacatul
Exemplu de monitor simplu
Lock lock;
Condition dataready;
Queue queue;

AddToQueue(item) {
lock.Acquire(); // Get Lock
queue.enqueue(item); // Add item
dataready.signal(); // Signal any waiters
lock.Release(); // Release Lock
}
RemoveFromQueue() {
lock.Acquire();// Get Lock
while (queue.isEmpty()) {
dataready.wait(&lock);
// If nothing, sleep and release lock }
item = queue.dequeue();// Get next item
lock.Release(); // Release Lock
return(item);
}
Solutia problema P/C cu monitoare
TEMA
Sfarsitul cursului 8

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