Documente Academic
Documente Profesional
Documente Cultură
Capitolul 4
2017 - 2018
Procese
• Algoritmii pentru sectiuni critice fac apel exclusiv la instructiunile
procesorului gazda.
• Semafoarele definesc o abstractie de programare la un nivel deasupra
instructiunilor masina, de regula la nivelul SO.
• Intr-un sistem multiprocesor sau multitasking mai multe procese pot
partaja resursele aceluiasi procesor.
• Un sistem contine o multime de procese, dintre care:
– Un proces in executie (engl. running)
– Mai multe procese gata de executie (engl. ready)
• O componenta a SO numita planificator (engl. scheduler) realizeaza
periodic realocarea procesorului unui nou proces gata de executie.
• Abstractizand modul de functionare a planificatorului, rezulta ca in
principiu este posibila orice intretesere a proceselor gata de executie.
2017 - 2018
Stari ale proceselor
• Fiecare proces p are alocata o stare p.state {inactive, ready, running,
blocked, completed}.
– Initial, dupa creare, procesul se afla in starea inactive
– Apoi procesul este activat si devine gata de executie in starea ready
– Din ready procesul poate trece in executie in starea running
– Din running procesul poate reveni in ready, se poate termina si trece in completed
sau poate inceta executia trecand in blocked.
– Din blocked procesul poate reveni in ready.
blocked
2017 - 2018
Definirea semafoarelor
• Un semafor S este o structura de date cu doua campuri:
– S.V de tip numar natural
– S.L de tip multime de procese
• Initial semaforul primeste o valoare k 0 pentru campul S.V si
multimea vida pentru campul S.L, adica:
semaphore S (k, )
• Pentru un semafor S pot fi definite doua operatii atomice:
wait(S)
signal(S)
• Numele date initial de Dijkstra pentru wait si signal sunt P si V.
Ele reprezinta initialele unor cuvinte in limba olandeza:
P = passering (trecere), V = vrijgave (eliberare)
2017 - 2018
Operatia wait
wait(S):
if S.V > 0
S.V S.V – 1
else
S.L S.L { p }
p.state blocked
• Daca S.V > 0 atunci se decrementeaza S.V si procesul curent p
poate continua executia.
• Daca S.V = 0 atunci procesul curent p este adaugat la multimea
S.L si p trece in starea blocked. Se spune ca p devine blocat la
semaforul S.
2017 - 2018
Operatia signal
signal(S):
if S.L =
S.V S.V + 1
else
fie un proces arbitrar q S.L
S.L S.L { q }
q.state ready
• Daca S.L = atunci se incrementeaza S.V.
• Daca S.L atunci se alege un proces arbitrar q S.L si se
deblocheaza fiind trecut in starea ready.
• Starea procesului p ce executa operatia nu se schimba.
2017 - 2018
Semafoare generale si binare
• Un semafor care poate lua o valoare reprezentand un numar
natural arbitrar pentru campul S.V se numeste semafor general
sau semafor numarator (engl. counting semaphore)
• Un semafor care poate lua doar una dintre valorile 0 sau 1
pentru campul S.V se numeste semafor binar. Operatia wait(S)
nu se schimba pentru un semafor binar. In schimb, pentru
operatia signal(S), instructiunea de incrementare a campului
S.V se inlocuieste cu S.V 1, iar semaforul poate fi initializat
doar cu (0,) sau (1,).
• Un semafor binar se mai numeste uneori si mutex (uneori
zavoarele folosite in monitoare se numesc mutex, asa ca pot
exista confuzii cu acest termen).
2017 - 2018
Sectiunea critica cu semafoare pentru 2 procese
Sectiunea critica cu semafoare pentru 2 procese
binary semaphore S (1,)
p q
loop forever loop forever
p1: sectiune non-critica q1: sectiune non-critica
p2: wait(S) q2: wait(S)
p3: sectiune critica q3: sectiune critica
p4: signal(S) q4: signal(S)
• Daca q este blocked in q1 atunci singurul proces care va putea avansa este
p. Rezultatul este ca q isi va putea continua executia din q2. Situatia este
similara pentru p.
• Nu exista nici o stare (p2,q2), deci excluderea mutuala este verificata.
• Nu exista o stare in care procesele sunt blocked, deci nu avem interblocaj.
• Daca p executa wait atunci el va progresa direct sau prin intermediul starii
blocked intr-o stare in care poate executa signal, deci nu avem infometare.
2017 - 2018
Invarianti pentru semafoare
• Teorema: Fie S un semafor, k0 valoarea initiala a lui S.V, #signal(S)
numarul de apeluri ale functiei signal(S) si #wait(S) numarul de apeluri
ale functiei wait(S). Atunci sunt valabili urmatorii doi invarianti:
S.V 0 (Inv1)
S.V = k + #signal(S) – #wait(S) (Inv2)
• Demonstratie:
– Deoarece initial S.V = k, k 0 si #signal(S) = #wait(S) = 0 rezulta ca Inv1 si Inv2 sunt
verificati.
– wait(S) eventual descreste S.V cand S.V > 0, iar signal(S) eventual creste S.V, deci
Inv1 este adevarat in mod trivial.
– Daca se executa wait(S) si S.V este decrementat atunci #wait(S) este incrementat.
– Daca se executa signal(S) si S.V este incrementat atunci #signal(S) este incrementat.
– Daca signal(S) deblocheaza un proces q, atunci procesul q termina executia unui
wait(S), astfel ca S.V ramane neschimbat, iar #wait(S) si #signal(S) sunt
imcrementate cu 1.
– Daca wait(S) blocheaza procesul curent atunci S.V, #wait(S) si #signal(S) nu se
schimba.
2017 - 2018
Excluderea mutuala in sectiunea critica cu semafoare
• Teorema: Implementarea sectiunii critice cu semafoare este
corecta: asigura excluderea mutuala, lipsa interblocajului si
lipsa infometarii.
• Demonstratie:
• Daca #cs e numarul de procese din sectiunea critica atunci
avem invariantul:
#cs + S.V = 1 (Inv3)
• Prin inductie din structura programului rezulta ca:
#cs = #wait(S) – #signal(S)
• Combinand acest invariant cu Inv2 pentru k = 1 rezulta Inv3.
Deoarece conform Inv1 avem S.V 0 rezulta ca #cs 1, deci
excluderea mutuala este satisfacuta.
2017 - 2018
Lipsa infometarii si interblocajului in sectiunea
critica cu semafoare pentru 2 procese
• Daca am avea interblocaj, inseamna ca cele doua procese p si q
ar trebui sa fie blocate in urma executiei apelului wait(S), adica
S.V = 0.
• Deci nici unul dintre cele doua procese nu se afla in SC, adica
#cs = 0. Rezulta ca #cs + S.V = 0, contradictie cu Inv3.
• Daca unul dintre procese, fie el p, ar fi infometat, atunci el va fi
blocat indefinit la semaforul S ce va avea valoarea (0,S.L) cu p
S.L. Conform Inv3, ar rezulta ca #cs = 1 – S.V = 1 – 0 = 1.
Deci obligatoriu celalalt proces q se afla indefinit in SC. Dar in
acest fel se contrazice presupunerea de progres a lui q in SC.
2017 - 2018
Sectiunea critica cu semafoare pentru N procese
• Algoritmii pentru sectiunea critica cu N procese pentru un N arbitrar
raman identici cu cazul N = 2.
• Se verifica excluderea mutuala si lipsa interblocajului.
• Nu se verifica lipsa infometarii.
# p q r S
1 p1: wait(S) q1: wait(S) r1: wait(S) (1,)
2 p2: signal(S) q1: wait(S) r1: wait(S) (0,)
3 p2: signal(S) q1: blocked r1: wait(S) (0,{q})
4 p2: signal(S) q1: blocked r1: blocked (0,{q,r})
5 p1: wait(S) q1: blocked r2: signal(S) (0,{q})
6 p1: blocked q1: blocked r2: signal(S) (0,{p,q})
7 p2: signal(S) q1: blocked r1: wait(S) (0,{q})
2017 - 2018
Semafoare slabe si semafoare tari
• Semafoarele prezentate se numesc si semafoare slabe (engl. weak
semaphores), Motivul este ca nu asigura lipsa infometarii datorita alegerii
arbitrare a procesului care va fi deblocat.
• Se pot defini semafoare tari (engl. strong semaphores) implementand pe
S.L utilizand o coada cu urmatoarele operatii:
– enqueue(Q,x): depune un element x in coada Q si intoarce o noua coada Q‟
– dequeue(Q): elimina un element x din coada Q si intoarce (x, Q‟) unde Q‟ este coada
rezultata prin eliminarea lui x din Q.
wait(S): signal(S):
if S.V > 0 if S.L =
S.V S.V – 1 S.V S.V + 1
else else
S.L enqueue(S.L, p) (q, S.L) dequeue(S.L)
p.state blocked q.state ready
2017 - 2018
Semafoare busy-wait
• Un semafor busy-wait nu contine componenta S.L iar S.V este identic cu S.
wait(S): signal(S):
await S > 0 SS+1
SS–1
• Un semafor busy-wait nu garanteaza lipsa infometarii in problema
sectiunii critice nici pentru cazul a 2 procese. In scenariul de mai jos q
este infometat, linia 4 este identica cu linia 1.
• q e selectat indefinit (echitabil), dar exact cand S = 0, a.i. nu va progresa.
# p q S
1 p1: wait(S) q1: wait(S) 1
2 p2: signal(S) q1: wait(S) 0
3 p2: signal(S) q1: wait(S) 0
4 p1: wait(S) q1: wait(S) 1
2017 - 2018
Semafoare cu valori negative
• Unii autori permit ca un semafor sa aiba valoarea S.V negativa. Aceasta
valoare se poate folosi pentru a determina, de exemplu, numarul de
elemente aflate in multimea S.L, dupa cum se va vedea mai jos
wait(S): signal(S):
S.V S.V – 1 S.V S.V + 1
if S.V < 0 if S.V 0
S.L S.L { p } fie un proces arbitrar q S.L
p.state blocked S.L S.L { q }
q.state ready
• Tema: Sa se demonstreze ca aceste definitii ale operatiilor cu semafoare
verifica invariantii urmatori:
(S.V 0) (|S.L| = – S.V)
(S.V 0) (|S.L| = 0)
S.V = k + #signal – #wait – |S.L|
2017 - 2018
Semafoare in platforma Java
• Platforma Java contine o implementare a semafoarelor in
pachetul de concurenta java.util.concurrent prin clasa
Semaphore.
• Operatiile P si V (wait si signal) se numesc acquire si release.
• Operatia acquire poate genera o exceptie InterruptedException.
• Primul parametru al constructorului ce defineste valoarea
initiala a semaforului este un numar natural ce se numeste
numar de permise.
• Constructorul clasei contine un parametru suplimentar boolean
utilizat pentru a indica daca semaforul este sau nu echitabil. Un
semafor echitabil corespunde definitiei de semafor tare.
2017 - 2018
Clasa Semaphore in Java
public class Semaphore {
public Semaphore(long permits);
public Semaphore(long permits, boolean fair);
public void acquire( ) throws InterruptedException;
P
public void acquireUninterruptibly( );
public void acquire(long permits) throws InterruptedException;
public void acquireUninterruptibly(long permits);
public boolean tryAcquire( );
public boolean tryAcquire(long timeout, TimeUnit unit);
public boolean tryAcquire(long permits);
public boolean tryAcquire(long permits, long timeout, TimeUnit unit);
public void release(long permits);
V
public void release( );
public long availablePermits( );
}
2017 - 2018
Numarare concurenta cu semafoare I
import java.util.concurrent.Semaphore;
public class CountSem extends Thread {
static volatile int n = 0;
static Semaphore s = new Semaphore(1);
public int getN() {
return n;
}
public void run() {
int temp;
for (int i=0; i<10000000; i++) {
try {
s.acquire();
temp = n;
n = temp+1;
}
catch (InterruptedException e) {}
finally { s.release(); }
}
}
} 2017 - 2018
Numarare concurenta cu semafoare II
import java.io.*;
public class MainCountSem {
public static void main(String[] args) {
CountSem p = new CountSem();
CountSem q = new CountSem();
p.start();
q.start();
try {
p.join();
q.join();;
}
catch (InterruptedException e) {}
System.out.println("Final counter: "+p.getN());
}
}
2017 - 2018
Alte mecansime Java pentru sincronizare
• Pe langa clasa Semaphore, Java include alte doua clase utile
pentru sincronizarea firelor de executie.
• Clasa CountDownLatch
• Clasa CyclicBarrier
• Clasa CountDownLatch permite ca unul sau mai multe fire sa
astepte pana cand o multime de operatii ale altor fire sunt
terminate. CyclicBarrier
• Tema: Cum se poate implementa functionalitatea clasei
CountDownLatch folosind semafoare?
2017 - 2018
Implementarea firelor ascultatoare
class Waiter implements Runnable {
CountDownLatch latch = null;
int idx;
public Waiter(CountDownLatch latch, int i) {
this.latch = latch;
idx = i; Asteapta indeplinirea conditiei
}
public void run() {
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Waiter "+idx+" released");
}
}
2017 - 2018
Implementarea firelor semnalizatoare
class Decrementer implements Runnable {
int counter, idx;
CountDownLatch latch = null;
public Decrementer(CountDownLatch latch,int i,int counter) {
this.latch = latch;
this.counter = counter;
idx = i;
}
public void run() { Semnalizarea evenimentului
try {
for (int i = 1; i <= counter; i++) {
Thread.sleep(1000);
latch.countDown();
System.out.println("Decrementer "+idx+": Step "+i);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} 2017 - 2018
Program de test CountDownLatch – structuri de date
• Se creaza firele folosind Runnable. Atunci avem doua tipuri de
obiecte “executabile”:
• waiters si decrementers, vectori de obiecte ce implementeaza Runnable
• waiter_threads si decrementer_threads, vectori de fire
public class Main {
private static Waiter[] waiters;
private static Decrementer[] decrementers;
private static CountDownLatch latch;
private static Thread[] decrementer_threads;
private static Thread[] waiter_threads;
private static int counter = 15; // Valoarea p
private static int n_waiters = 20; // Valoarea n
private static int n_decrementers = 3; // Valoarea m. k = p/m = 5
// Codul functiei main ...
}
2017 - 2018
Program de test CountDownLatch – functia main
public static void main(String args[]) {
latch = new CountDownLatch(counter);
// Creare waiters si decrementers
decrementers = new Decrementer[n_decrementers];
decrementer_threads = new Thread[n_decrementers];
waiters = new Waiter[n_waiters];
waiter_threads = new Thread[n_waiters];
for (int i=0; i < n_decrementers; i++) {
decrementers[i] = new Decrementer(latch,i+1,counter / n_decrementers);
decrementer_threads[i] = new Thread(decrementers[i]);
}
for (int i=0; i < n_waiters; i++) {
waiters[i] = new Waiter(latch,i+1);
waiter_threads[i] = new Thread(waiters[i]);
}
// Pornire fire
for (int i=0; i < n_waiters; i++) {
waiter_threads[i].start();
}
for (int i=0; i < n_decrementers; i++) {
decrementer_threads[i].start();
}
// Asteapta terminarea firelor folosind join ... tema !!!
} 2017 - 2018
Clasa CyclicBarrier
• Clasa CyclicBarrier permite firelor dintr-o multime de fire sa astepte pana cand
fiecare dintre ele ajunge intr-un punct comun de bariera. Obiectul poate fi
refolosit odata ce firele in asteptare au fost eliberate sa continue – bariera ciclica.
• Numarul firelor ce folosesc bariera este fix si se specifica in constructor.
public class CyclicBarrier {
public CyclicBarrier(int parties);
public CyclicBarrier(int parties, Runnable barrierAction);
public int await( ) throws
InterruptedException, BrokenBarrierException;
public int await(long timeout, TimeUnit unit) throws
InterruptedException, BrokenBarrierException, TimeoutException;
public void reset( );
public boolean isBroken( );
public int getParties( );
public int getNumberWaiting( );
}
• Fiecare fir care foloseste bariera, asteapta la bariera prin executia metodei await.
• Cand toate firele au executat await, bariera este “eliberata”, putand fi refolosita.
• Bariera poate specifica o actiune Runnable ce se va executa automat la eliberare.
2017 - 2018
Teme
• Tema 1:
• Cum se poate implementa clasa CyclicBarrier folosind clasa
CountDownLatch ?
• Cum se poate implementa clasa CountDownLatch folosind clasa
Semaphore ?
• Tema 2: Se considera un vector 𝑣 de 𝑛 = 100000 de numere. Sa se
determine cel mai mare numar din 𝑣 folosind 𝑚 = 10 fire. Fiecare fir
𝑖 ∈ *1,2, … , 𝑚+ determina cel mai mare numar 𝑤𝑖 dintr-un subvector de
𝑝 = 𝑛/𝑚 elemente consecutive ale vectorului 𝑣 astfel: firul 1 pentru
subvectorul 𝑣1 , 𝑣2 , … , 𝑣𝑝 , firul 2 pentru subvectorul 𝑣𝑝+1 , 𝑣𝑝+2 , … , 𝑣2𝑝 ,
..., firul 𝑚 pentru subvectorul 𝑣 𝑚−1 𝑝+1 , 𝑣 𝑚−1 𝑝+2 , … 𝑣𝑛 . Se va folosi o
bariera pentru sincronizarea celor 𝑚 fire. Rezultatul se va determina
calculand ulterior minimul vectorului 𝑤. Se observa ca aceasta metoda are
potentialul de a reduce timpul de executie al algoritmului de la 𝑂(𝑛) la
𝑛
𝑂( + 𝑚). De ce?
𝑚
2017 - 2018
Semafoare POSIX I
2017 - 2018
Semafoare POSIX II
• Asteptarea la un semafor:
int sem_wait(sem_t *sem);
Daca valoarea semaforului este negativa, procesul apelant se blocheaza;
unul dintre procesele blocate va fi “trezit” cand un alt proces executa
sem_post. Implementeaza operatia P.
2017 - 2018
Exemplu cu semafoare POSIX I
#include <pthread.h>
#include <semaphore.h>
#define NUM_THREADS 20
int shared ;
sem_t binary_sem ; // Used like a mutex.
int thread_id[NUM_THREADS];
void *thread_function(void *arg) {
int i = *((int *)arg);
int t;
// Delay before accessing the shared resource
for (t=0;t<50000*i;t++) {}
sem_wait(&binary_sem) ; // Decrements semaphore count.
// Use shared resource.
printf("Incrementing by thread %d\n",i);
t = shared;
shared = t+1;
sem_post(&binary_sem) ; // Increments semaphore count.
}
2017 - 2018
Exemplu cu semafoare POSIX II
int main () {
pthread_t thread[NUM_THREADS];
int i,t;
// Share by threads. Set semaphore to initial count 1.
sem_init(&binary_sem, 0, 1);
// Start threads here.
for (i = 0; i < NUM_THREADS; i++) {
thread_id[i] = i;
if (pthread_create(&thread[i], NULL, thread_function,
(void *)&thread_id[i]) != 0 ) {
printf("pthread_create() error\n");
return 1;
}
else {
printf("Thread %d created\n",i);
}
}
2017 - 2018
Exemplu cu semafoare POSIX III
sem_wait(&binary_sem);
// Use shared resource.
printf("Incremeting by main\n");
t = shared;
shared = t+1;
sem_post(&binary_sem);
sem_destroy(&binary_sem);
printf("%d\n",shared);
return 0 ;
}
2017 - 2018
Problema producator - consumator
• Contine doua tipuri de procese:
– Producatori: executa instructiuni ce produc elemente de date, ce
urmeaza a fi trimise la procesele consumator.
– Consumatori: dupa receptionarea unui element de date de la procesele
producator, executa o instructiune ce consuma elementul respectiv
• Problema producator – consumator apare in numeroase situatii
in sistemele de calcul concurente si distribuite:
– Un client Web primeste / trimite date de la / catre nivelul de
comunicatie in retea
– Driver-ul unui dispozitiv de intrare transmite date catre sistemul de
operare
– Un procesor de texte trimite date catre un driver de imprimanta.
Ulterior, driver-ul de imprimanta trimite date catre imprimanta.
– Etc.
2017 - 2018
Zona tampon in problema producator - consumator
• Comunicarea intre producator si consumator poate fi sincrona
sau asincrona.
• In comunicarea sincrona, producatorul si consumatorul trebuie
sa se coordoneze. Comunicarea nu va avea loc decat atunci
cand ambele procese sunt gata sa schimbe un element de date.
• In comunicarea asincrona, schimbul de informatie are loc
printr-o zona tampon numita buffer. Tamponul are functiile
unei cozi: producatorul depune elemente in spatele cozii, iar
consumatorul preia elemente din fata cozii.
• Exista doua probleme ce necesita atentie:
– Consumatorul nu poate prelua un element dintr-un tampon gol
– Producatorul nu poate depune un element intr-un tampon plin
2017 - 2018
Producator – consumator cu zona tampon finita
Producator – consumator cu semafoare si zona tampon finita
semaphore notEmpty (0,)
semaphore notFull (N,)
finite queue of dataType buffer empty queue
p q
dataType d dataType d
loop forever loop forever
p1: d produce q1: wait(notEmpty)
p2: wait(notFull) q2: d take(buffer)
p3: append(d,buffer) q3: signal(notFull)
p4: signal(notEmpty) q4: consume(d)
furc1
• Un filosof poate apuca furculitele numai una cate una si are nevoie de
doua furculite sa poata manca.
• Proprietati de corectitudine:
– Un filosof mananca numai cand are doua furculite
– Excludere mutuala: doi filosofi nu pot detine simultan aceeasi furculita
– Lipsa interblocajului
– Lipsa infometarii
– Comportament eficient in lipsa conflictelor pe furculite 2017 - 2018
Problema cinei filosofilor – varianta I
Cina filosofilor (varianta I) • Teorema: Nici o furculita nu va fi
Filosof i ( i {0,1,2,3,4} ) detinuta simultan de doi filosofi.
semaphore array[0..4] fork Demonstratie:
[1,1,1,1,1] • Daca #Fi este numarul de filosofi
ce detin furculita i atunci #Fi =
loop forever #wait(fork[i]) – #signal(fork[i]).
p1: think Din invariantul semaforului
p2: wait(fork[i]) fork[i] rezulta ca fork[i].V = 1 –
p3: wait(fork[i+1]) #Fi de unde #Fi = 1 – fork[i].V.
p4: eat Dar fork[i].V 0 de unde #Fi 1.
p5: signal(fork[i])
p6: signal(fork[i+1])
2017 - 2018
Teme II