Sunteți pe pagina 1din 16

Semafoare

Noiunea de semafor
Una dintre modalitile de a rezolva mai simplu problema excluderii reciproce, precum i
alte probleme din programarea concurent, este utilizarea semafoarelor.
Noiunea de semafor a fost introdus n 1968 de ctre Dijkstra.
Semaforul este un tip de date, caracterizat deci prin valorile pe care le poate lua i operaiile
n care poate interveni.
O variabil de tip semafor (general) poate lua ca valori doar numere naturale. Dac valorile
permise pot fi numai 0 sau 1, spunem c este vorba de un semafor binar.
Conceptual, unui semafor s i este asociat o mulime de blocare Bs, iniial vid.
Operaiile definitorii pentru semafoare sunt urmtoarele:
1) wait(s) : dac valoarea semaforului s este diferit de zero (pozitiv), atunci se micoreaz
cu o unitate aceast valoare i se trece mai departe; n caz contrar procesul care execut aceast
operaie este blocat n Bs;
2) signal(s) : dac exist procese blocate de semaforul s, unul dintre ele este deblocat i
trecut n starea "gata de executare"; n caz contrar valoarea lui s este mrit cu o unitate.
Operaiile wait i respectiv signal mai sunt notate cu P i respectiv V (iniialele
cuvintelor din limba olandez semnificnd testare i incrementare).
Este important de precizat c fiecare dintre cele dou operaii este considerat o aciune
atomic (se mai spune c este vorba de primitive). Aceasta nseamn c de exemplu pentru
operaia wait eventuala succesiune comparare + decrementare este indivizibil. Analog, n
cazul
operaiei
signal
succesiunile
comparare+deblocare
i
comparare+incrementare sunt indivizibile. Este uor de anticipat c aceasta va permite
rezolvarea simpl a problemei excluderii reciproce.
Mai precizm c n timp ce dou operaii asupra aceluiai semafor se execut prin excludere
reciproc, pentru dou operaii asupra unor semafoare distincte o suprapunere a executrii lor este
posibil.
n cele ce urmeaz dm o imagine plastic a modului de funcionare a unui semafor.
Considerm o parcare pentru maini prevzut cu o intrare i o ieire. Activitile din
parcare sunt dirijate de un paznic, care autorizeaz intrrile (respectiv ieirile) mainilor n (respectiv
din) parcare i ine minte numrul s de locuri libere. n continuare artm c activitatea paznicului
este similar cu funcionarea unui semafor.
Fiecare ofer, nainte de a ncerca s intre sau s ias din parcaj, anun mai nti paznicul.
Acesta, fiind un om obinuit, nu poate discuta simultan cu mai multe persoane, deci niciun ofer nu
se poate adresa paznicului n intervalul de timp n care acesta dialogheaz cu un alt ofer
(asigurndu-se astfel excluderea reciproc).
Exist dou modaliti prin care paznicul discut cu oferii, corespunztoare celor dou
primitive asupra semafoarelor:
- wait(s) : Un ofer anun paznicul c dorete s intre n parcare. Paznicul verific dac
exist locuri libere. n caz afirmativ, oferului i se permite accesul, maina ocup unul dintre locurile
libere, valoarea lui s se micoreaz cu o unitate i dialogul se ncheie. n caz contrar, paznicul
comunic oferului c nu poate intra deoarece nu exist locuri libere, dar c a notat numrul mainii,
invitndu-l s fie gata s intre n parcar atunci cnd i va face semn; dialogul se ncheie.
- signal(s) : Un ofer dorete s ias din parcaj i ncepe dialogul cu paznicul prin a l
informa de aceast intenie. Dac exist oferi care ateapt s intre n parcare, atunci paznicul i
1

permite s ias din parcare oferului care a iniiat dialogul, concomitent (pentru operativitate)
fcnd semn s intre unuia dintre oferii aflai pe lista de ateptare a paznicului; dialogul se ncheie.
n caz contrar, paznicul autorizeaz prsirea parcajului de ctre oferul ce a iniiat dialogul, mrete
cu o unitate valoarea lui s i ncheie dialogul.
Pe lng cele dou primitive prezentate mai sus, singura operaie permis (i necesar)
asupra semafoarelor este atribuirea unei valori iniiale.
Recapitulnd, descriem n pseudocod tipul semafor:
Valori posibile: numere naturale.
Declararea unei variabile de tip semafor:
var s:semaphore;

Operaii permise:
1) initial(s,s0)
2) wait(s) :
if s>0
then ss-1
else procesul curent este blocat
3) signal(s) :
if exist procese blocate
then unul dintre ele este deblocat
else ss+1
Operaiile wait i signal asupra aceluiai semafor se execut prin excludere reciproc.

o Invariani
Pentru un semafor s oarecare, urmtoarele relaii sunt invariante:
s0
(1)
s=s0+#(signal)-#(wait)
(2)
unde s0 este valoarea iniial a semaforului, #(wait) indic de cte ori s-a trecut complet de
semafor (nu se numr deci procesele temporar blocate la semafor), iar #(signal) este numrul
operaiilor signal executate. Relaiile (1) i (2) sunt invariante n sensul c sunt ndeplinite la orice
moment de timp.
S verificm de exemplu relaia (2). Ea este satisfcut la iniializarea semaforului. Fie un
moment de timp oarecare la care relaia este ndeplinit i s considerm prima operaie ulterioar
asupra semaforului. Distingem cazurile:
- se execut o operaie wait: dac s=0 atunci cantitile ce intervin n relaie rmn
neschimbate; dac s>0 atunci s scade cu o unitate, iar #(wait) crete cu o unitate;
- se execut o operaie signal: dac exist procese blocate de semaforul s atunci #(signal)
i #(wait) cresc cu o unitate, valoarea lui s rmnnd astfel neschimbat; n caz contrar,
#(signal) i s cresc cu o unitate.
o Excluderea reciproc
Pentru realizarea excluderii reciproce, vom plasa seciunea critic ntre "parantezele" wait
i signal:
wait(s);
SC
signal(s);
SNC

Semaforul s trebuie iniializat cu 1: dac ar fi iniializat cu 0, n momentul n care oricare


2

dintre procese ar ncerca s intre n seciunea sa critic, el ar fi suspendat la executarea primitivei


wait(s), contrazicndu-se astfel condiia de competiie constructiv; dac ar fi iniializat cu o
valoare mai mare dect 1, atunci dou procese care doresc amndou s intre n seciunea lor critic
vor reui acest lucru, contrazicndu-se condiia de excludere reciproc.
n continuare vom considera numai momentele de timp cnd toate procesele sunt, ntr-o
distribuie oarecare, n seciunile lor critice, necritice sau sunt blocate (deci nici unul dintre procese
nu este n curs de executare a primitivelor wait i signal); de exemplu dup ce un proces a
executat instruciunea signal, el este considerat deja ca fiind n seciunea necritic i nu urmnd s
intre n aceasta.
Pentru a demonstra corectitudinea algoritmului, vom arta c:
(3)
este o relaie invariant, unde #(SC) este numrul de procese ce se afl n seciunea lor critic.
innd cont de relaia invariant (2) i de faptul c s0=1, este suficient s demonstrm c:
#(SC)=#(wait)-#(signal).
Aceast ultim relaie este iniial adevrat i rmne adevrat att la intrarea oricrui
proces n seciunea critic (se mresc concomitent #(SC) i #(wait)), ct i la intrarea oricrui
proces n seciunea sa necritic (se micoreaz #(SC) i se mrete #(signal)).
Din relaia (3) rezult c este ndeplinit condiia de excludere reciproc (#(SC)1), precum
i cea de competiie constructiv (dac nici un proces nu este n seciunea critic, atunci s=1 i deci
unul dintre ele va intra sigur). Condiia de conexiune liber este i ea evident satisfcut.
s+#(SC)=1

O implementare simpl a semafoarelor


O prim implementare este oferit de urmtoarea clas:
class Semafor {
private int val = 0;
public Semafor(int initial) { val = initial; }
public synchronized void W() {
val--;
if (val < 0) try { wait(); }
catch(InterruptedException e) { }
}
public synchronized void S() {
val++;
if (val <=0 ) notify();
}
}

Observaii:
- constructorul clasei realizeaz, odat cu crearea unui obiect, i iniializarea valorii val a
semaforului;
- metodele W i S corespund primitivelor wait i signal descrise mai sus, ce opereaz
asupra semafoarelor;
- fie sem un semafor, deci un obiect de tipul Semafor. Atunci operaiile wait(sem) i
signal(sem) din modelul general propus de Dijkstra vor trebui nlocuite respectiv prin sem.W()
i sem.S().
Propoziie. Fie s un semafor (obiect de tipul clasei Semafor). Exist procese blocate prin
metoda W n mulimea Ws dac i numai dac val<0; n acest caz, numrul lor este -val.
3

Afirmaia este evident adevrat la momentul iniial (crearea semaforului s).


Cnd un fir execut metoda W prin intermediul semaforului s:
dac val>0, atunci val-- i metoda se ncheie;
dac val<=0, atunci val-- i firul este blocat n Ws; numrul firelor blocate este -val.
Cnd un fir execut metoda S prin intermediul semaforului s:
dac nu exist fire blocate la semaforul s, atunci val++ i metoda se ncheie;
dac exist fire blocate la semaforul s, atunci val++, corespunztor faptului ca un fir din Ws
trece n Ms prin executarea lui notify(); terminarea metodei face ca un fir din Ms s treac n
starea "gata de executare". Drept urmare, fiecare dintre firele blocate prin W are ansa ca la un
moment dat s-i reia activitatea i s termine de executat metoda W.
Exemplul 1. Problema grdinilor ornamentale.

Relum problema grdinilor ornamentale. n plus vom crea un bazin de fire de executare
cu numr fix de fire (am prezentat anterior noiunea de bazin fire de executare).
Vom folosi dou semafoare: ER (pentru excludere reciproc) i s (pentru simularea
bazinului de fire).
class C {
static int n; int r;
static Semafor s = new Semafor(3);
static Semafor ER = new Semafor(1);
void incr() {
ER.W();
r = n; r++; n = r;
ER.S();
}
}
class Tip extends Thread {
C Ob = new C(); int i;
Tip(int i) { this.i = i; }
public void run() {
C.s.W();
try {
for (int j=1; j<=20; j++) {
Thread.sleep( (int) (50*Math.random()) );
Ob.incr(); System.out.print("
"+i);
}
}
catch(InterruptedException ie) { }
C.s.S();
}
}
class AtribMult1 {
public static void main(String[] s)
throws InterruptedException {
Tip[] T = new Tip[10];
for (int i=0; i<10; i++) T[i] = new Tip(i);
for (int i=0; i<10; i++) T[i].start();
for (int i=9; i>=0; i--) T[i].join();
System.out.println("\nn = " + C.n);
}
4

o Jocul vieii
Un tabel dreptunghiular a cu m linii i n coloane conine celule care pot fi vii sau moarte (1
sau 0). Fiind dat o configuraie a tabelului, se cere s se calculeze configuraia "generaiei"
urmtoare, tiind c celulele moarte i pstreaz starea, iar o celul vie moare dac i numai dac
numrul celulelor vecine vii este mai mic dect 2 sau mai mare dect 4.
O celul are cel mult 8 vecini: pe orizontal, pe vertical i pe diagonale.
Pentru un calcul facil al numrului vecinilor vom borda matricea cu zerouri.
Vom asocia fiecrei celule un proces n care apare o variabil local k. O prim aciune
const n a calcula (evident numai pentru celulele vii) numrul k al celulelor vecine vii. O a doua
aciune const n actualizarea strii fiecrei celule pe baza valorii k calculate.
Este evident ns c dac actualizarea unei stri se face nainte ca o stare vecin s i fi
calculat valoarea k, atunci este posibil ca rezultatul final s fie denaturat, ca de exemplu n situaia
urmtoare:
0
1
2
3

0
1

1
1

n care, dac celula de pe poziia (0,0) se "grbete" i moare, va muri i celula de pe poziia (1,1).
Jocul vieii evidenieaz urmtorul tip de sincronizare: mai multe procese ce se execut
concurent trebuie s se atepte unul pe altul pn cnd toate i ncheie o anumit activitate.
Trebuie deci realizat efectul metodei join din Java.
Celulele, prin procesele Celula(i,j), i vor desfura activitatea concurent cu procesul
Control.
Sunt folosite semafoarele s (pentru excludere reciproc, deci iniializat cu 1) i sem
(iniializat cu 0). Variabila global nr numr cte celule i-au calculat vecinii.
Procesul Celula(i,j)
if a[i,j]=1
then k a[i,j+1]+a[i-1,j+1]+a[i-1,j]+a[i-1,j-1]+
a[i,j-1]+a[i+1,j-1]+a[i+1,j]+a[i+1,j+1];
wait(s); nr nr+1; signal(s);
wait(sem);
if a[i,j]=1
then if (k<2) or (k>4)
then a[i,j] 0
Procesul Control
while nr<m*n do ;
for i:=1 to m*n do
signal(sem)
Observaii:
- sunt introduse doar dou semafoare, indiferent de dimensiunile tabelului de celule;
- semaforul sem are n permanen valoarea 0 (joac rolul unei bariere).
o Problema Productor - Consumator
5

Relum aceat problem, rezolvat anterior cu monitoare.


Fie lung lungimea benzii. Vom folosi semafoarele libere i ocupate, a cror valoare
corespunde numrului de celule libere, respectiv ocupate de pe band; corespunztor, ele vor fi
iniializate cu lung (lungimea benzii), respectiv 0.
S observm c este verificat relaia libere+ocupate=lung; aceasta nu permite ns
folosirea unui singur semafor!
Procesul Prod
for ch='a','z'
wait(libere);
wait(ExRec);
pune(ch); write("
signal(ExRec);
signal(ocupate)
end

P" + ch);

Procesul Cons
repeat
wait(ocupate);
wait(ExRec);
ia(ch); write("
signal(ExRec);
signal(libere)
until ch='z'

P" + ch);

o Problema filozofilor chinezi


La o mas rotund stau nf filozofi chinezi, iar ntre fiecare doi filozofi vecini se afl un
beior.

F3

b3

F2
b2

b4

F1

F4
b

F0

b1

Fiecare filozof execut 0n mod repetat secvena de aciuni (gndete, mnnc), pentru
intervale variabile de timp:
- ia beiorul din stnga;
- ia beiorul din dreapta;
- folosete beioare pentru a mnca din castronul cu orez din centrul mesei;
- pune pe mas beiorul din stnga;
- pune pe mas beiorul din dreapta.
Rspunsul la ntrebarea "Ce se poate ntmpla ru" are dou aspecte:
- doi filozofi vecini pot lua amndoi beiorul dintre ei, deci trebuie asigurat excluderea
reciproc asupra beioarelor;
- se poate ajunge la blocare total (deadlock) n situaia n care toi filozofii au ridicat beiorul
din stnga lor; trebuie deci evitat aceast situaie.
Fiecrui filozof i i asociem un proces Fi.
6

Pentru rezolvarea excluderii reciproce vom asocia fiecrui beior i un semafor binar bi,
iniializat cu 1; ncercarea de ridicare a unui beior corespunde interogrii semaforului asociat lui.
Pentru evitarea blocrii totale introducem semaforul AccesLiber, care va memora n
permanen numrul de filozofi crora le dm voie s ncerce s mnnce. Iniializarea sa se poate
face cu orice valoare ntreag din intervalul [1,nf-1], dar (pentru maximum de libertate) vom
alege valoarea nf-1.
Filozoful Fi
repeat

filozoful i gndete
wait(AccesLiber);
wait(b[i]);
wait(b[(i+1) mod nf]);
write(" M" + i);
filozoful i mnnc
write(" G" + i);
signal(b[i]);
signal(b[(i+1) mod nf]);
signal(AccesLiber)
until false

Este evident c mesajul de nceput al activitii de a mnca trebuie inserat imediat dup ce se
trece de semafoarele care controleaz posibilitatea de a fi ridicate beioarele din stnga i dreapta
filozofului.
Mai puin evident este c mesajul de nceput al activitii de a gndi trebuie inserat imediat
nainte (i nu dup) semnalrile repunerii beioarelor pe mas. Dac mutm acest mesaj dup
operaiile signal ce corespund repunerii beioarelor pe mas, lucrurile se vor desfura corect
din punctual de vedere al cerinelor problemei, dar incorect din punctul de vedere al semnalrii
aciunilor; ntr-adevr, pe ecran poate s apar secvena:
G4

G3

G1

G5 G2 M1 M2 G1 ...
deoarece mesajul "G1 " apare dup ce filozoful 1 repune beioarele pe mas, ntre timp filozoful 2

ncepnd s mnnce.
Exemplul 2. Implementarea cu semafoare a problemei filozofilor chinezi.
import java.util.*;
class Fil extends Thread {
static int n;
static Semafor[] b; static Semafor AccesLiber;
static void delay(int i) {
try { Thread.sleep( (int) (i*Math.random()) ); }
catch(InterruptedException e) { }
}
int id,k;
Fil(int i) { id = i; }
public void run() {
for(k=0; k<10; k++) {
AccesLiber.W();
b[id].W();
b[ (id+1) % n ].W();
System.out.print("M" + id + "
delay(100);
System.out.print("G" + id + "
b[id].S();

");
");

b[ (id+1) % n ].S();
delay(100);
AccesLiber.S();
}
}
}
class FilSem {
public static void main(String[] qqq) {
int i;
Scanner sc = new Scanner(System.in);
System.out.print("Nr. filozofi = ");
Fil.n = sc.nextInt();
Fil.b = new Semafor[Fil.n];
for(i=0; i<Fil.n; i++) Fil.b[i] = new Semafor(1);
Fil.AccesLiber = new Semafor(Fil.n-1);
Fil[] filozofi = new Fil[Fil.n];
for(i=0; i<Fil.n; i++) filozofi[i] = new Fil(i);
for(i=0; i<Fil.n; i++) filozofi[i].start();
}
}

Clasa Semaphore
public class Semaphore extends Object implements Serializable

Aceast clas permire lucrul cu semafoare. Valoarea unui semafor reprezint numrul de
permisiuni de a intra n seciunea critic (de a accesa resurse comune). ncercarea de a accesa
resursele comune se face prin invocarea metodei acquire(), iar renunarea la aceast operaie se
face prin invocarea metodei release(); cele dou metode corespund lui wait i signal din
definiia general a semafoarelor.
Un semafor iniializat cu 1, numit i semafor binar, permite realizarea excluderii reciproce.
Unul dintre constructorii clasei permite i specificarea unui parametru de echitate
(fairness), care - dac este setat pe true - asigur deblocarea conform unei discipline de coad.
Nu este impus ca orice release() s fie precedat o invocare acquire(). Utilizarea
corect a semfoarelor ine de aplicaia concret.
Spre deosebire de alte mecanisme de programare concurent, semafoarele (care nu au
noiunea de posesie a unui lact) au proprietatea c "lactul" poate fi deschis de un alt fir (nu
exist restricia ca metodele acquire i release asupra aceluiai semafor s apar n acelai
fir).
Cei doi constructori ai clasei sunt:
public Semaphore(int n)
iniializeaz la n valoarea semaforului i nu asigur fairness la deblocarea firelor prin
release(). Dac n<0, sunt necesare invocri release() nainte ca o invocare s

realizeze accesul la seciunea critic.


public Semaphore(int n, boolean fair)

difer de constructorul anterior prin aceea c dac al doilea argument este true, la deblocare
este asigurat disciplina de coad. Aceast disciplin este ns relativ la puncte de executare
specifice din metodele acquire i release, ceea ce poate conduce la ordini ntructva
diferite de cele ateptate; important este ns c n mod sigur este evitat situaia de
"starvation" (amnare infinit), adic euarea continu de a accesa o resurs.
Prezentm n continuare principalele metode ale clasei.
public void acquire() throws InterruptedException
8

Dac valoarea semaforului este pozitiv, aceast valoare este decrementat cu o unitate. n
caz contrar, firul este blocat pn cnd un alt fir execut release() i firul curent este cel
ales s treac n starea gata de executare. Este echivalentul lui wait.
Excepia este lansat dac firul este n starea "ntrerupt" la intrarea n metod sau dac este
ntrerupt n timp ce ateapt permisiunea de a intra n seciunea critic; n aceste cazuri firul
iese din starea "ntrerupt".
public boolean tryAcquire()

ntoarce rezultatul ncercrii de a obine o permisiune. Dac este posibil s obin o


permisiune, o obine chiar dac semaforul urmeaz politica de fairness (deci o obine naintea
altor fire care sunt blocate) i decrementeaz valoarea semaforului; n acest caz valoarea
ntoars este true. Dac nu se poate obine o permisiune, metoda ntoarce valoarea false i
firul care a invocat-o i continu activitatea.
public void release()

Elibereaz o permisiune, incrementnd numrul permisiunilor cu o unitate. Dac exist fire ce


ateapt o permisiune, este ales unul dintre ele; el capt permisiunea (deci valoarea
semaforului scade cu o unitate) i trece n starea gata de executare. Este echivalentul lui
signal.
public int availablePermits()

ntoarce valoarea curent (numrul curent de permisiuni) a semaforului; este folosit de obicei
pentru depanare.
Clasa Semaphore pune la dispoziie i metode pentru cereri/eliberri de permisiuni
multiple, dintre care unele sunt prezentate n continuare. Este recomandat s fie folosite n modul
fair, pentru a nu conduce la amnare infinit.
Excepia IllegalArgumentException este lansat dac p<0.
public void acquire(int p) throws InterruptedException,
IllegalArgumentException
Dac valoarea semaforului mai mare sau egal cu p, aceast valoare este decrementat cu p.

n caz contrar, firul iese din schema de programare a firelor pn cnd fie un alt fir execut
release(), firul curent este cel ales s-i reia activitatea i valoarea semaforului este cel
puin egal cu p, fie un alt fir ntrerupe pe cel curent.
Excepia InterruptedException este lansat dac firul este n starea "ntrerupt" la
intrarea n metod sau dac este ntrerupt n timp ce ateapt permisiunea de a intra n
seciunea critic; n aceste cazuri firul iese din starea "ntrerupt"; toate permisiunile acordate
acestui fir sunt transferate altor fire care ateapt s obin permisiuni, ca i cnd au devenit
disponibile permisiuni prin executarea unui release().
public boolean tryAcquire(int p) throws IllegalArgumentException
Dac valoarea semaforului este cel puin p, sunt obinute p permisiuni (vezi i observaia de
la forma fr argumente a metodei), valoarea semaforului este decrementat cu p i rezultatul
ntors este true. n caz contrar, valoarea semaforului rmne neschimbat i rezultatul ntors
este false.
public void release(int p) throws IllegalArgumentException
Incrementeaz cu p valoarea semaforului. Ct timp valoarea semaforului este strict pozitiv i

exist fire blocate la semafor, se execut urmtoarele aciuni:


- este ales unul dintre firele blocate; fie p' numrul de permisiuni de care are nevoie;
- i se atribuie min(p,p') permisiuni, cu actualizrile de rigoare; dac i s-au atribuit p'
permisiuni, firul reintr n schema de gestiune a firelor.
Observaii:
- metodele release cu i fr argumente lucreaz diferit, deci (cred c) trebuie lucrat ori cu
permisiuni unice, ori cu permisiuni multiple !
9

- metodele tryAcquire (cu cerere de permisiune unic sau multipl) au cte o variant pentru
timp real.
Exemplul 3. Fie s,t dou tablouri cu ns i respectiv nt elemente. Se cere s se elaboreze
un program concurent care s nscrie n s cele mai mici ns, iar n t cele mai mari nt elemente din
cele dou tablouri. Singura operaie posibil ce implic un element din s i un element din t este
interschimbarea lor.
Se urmeaz ideea de la problema Productor - Consumator cu o band de lungime 1.
La fiecare pas, firul FirS calculeaz maximul valorilor din s i l transmite (l pune la
dispoziia) firului FirT. Acesta calculeaz minimul valorilor din t i, dac valoarea primit este mai
mare dect minimul, se face interschimbare. Facem observaia c, ntruct lungimea benzii este 1,
aciunile firelor FirS i FirT se succed n mod repetat n aceast ordine.
Entitile comune apar n clasa C. Semafoarele s1 i s2 corespund semafoarelor libere i
ocupate din problema Productor - Consumator, fiind iniializate i folosite ca atare.
import java.util.*;
import java.util.concurrent.*;
class MinMax {
public static void main(String[] www) throws Exception {
S FirS = new S(); T FirT = new T();
FirS.start(); FirT.start();
FirS.join(); FirT.join();
C.scrie();
}
}
class C {
// contine entitatile comune
static Scanner sc = new Scanner(System.in);
static int[] s,t; static int ns,nt;
static int ks,kt,maxs,mint;
static boolean continua=true;
static Semaphore s1 = new Semaphore(1),
s2 = new Semaphore(0);
static void scrie() {
for(int i=0; i<ns; i++) System.out.print(s[i] + "\t");
System.out.println();
for(int i=0; i<nt; i++) System.out.print(t[i] + "\t");
}
}
class S extends Thread {
S() {
System.out.print("ns = ");
C.ns = C.sc.nextInt();
C.s = new int[C.ns];
System.out.print("Dati cele ns=" + C.ns +" elemente: ");
for(int i=0; i<C.ns; i++) C.s[i] = C.sc.nextInt();
}
public void run() {
try {
while(C.continua) {
C.s1.acquire();
C.ks = 0; C.maxs=C.s[0];
10

for(int i=1; i<C.ns; i++)


if(C.s[i]>C.maxs) { C.maxs = C.s[i]; C.ks = i; }
C.s2.release();
}
}
catch(Exception e) { }
}
}
class T extends Thread {
T() {
System.out.print("nt = "); C.nt = C.sc.nextInt();
C.t = new int[C.nt];
System.out.print("Dati cele nt=" + C.nt +" elemente: ");
for(int i=0; i<C.nt; i++) C.t[i] = C.sc.nextInt();
}
public void run() {
try {
while(C.continua) {
C.s2.acquire();
C.kt = 0; C.mint=C.t[0];
for(int i=1; i<C.nt; i++)
if(C.t[i]<C.mint) { C.mint = C.t[i]; C.kt = i; }
if(C.maxs<=C.mint) C.continua = false;
else {
int x = C.s[C.ks]; C.s[C.ks] = C.t[C.kt];
C.t[C.kt] = x;
}
C.s1.release();
}
}
catch(Exception e) { }
}
}

Observaie. Dei este respectat condiia ca singura operaie ce implic un element din s i
un element din t s fie interschimbarea lor, soluia de mai sus are dezavantajul c ambele fire au
acces la ambele tablouri.
o Problema Cititori - Scriitori
Se consider o carte la care au acces mai muli cititori i mai muli scriitori. Este permis ca
mai muli cititori s aib acces simultan la coninutul crii, dar dac un scriitor opereaz asupra
crii (completeaz, terge, modific etc.) atunci nici un alt scriitor i nici un alt cititor nu au acces la
carte. Mai precis:
1) un cititor poate ncepe operaia de citire dac i numai dac nici un scriitor nu este n curs de
a scrie n carte;
2) un scriitor poate ncepe operaia de scriere dac i numai dac nici un cititor i nici un alt
scriitor nu au acces la carte.
Cu alte cuvinte, dac cuplul (nrcit,nrscr) ine evidena nunrului de cititori activi i
numrul de scriitori activi, valorile permise ale cuplului sunt numai:
(0,0), (0,1) i (n,0) cu n>0.
ncercrile de deschidere a crii au anse de reuit doar dac nici un scriitor nu este activ.
n acest caz ele se mpart n dou categorii:
a) cele de citire cnd exist cel puin un cititor activ;
11

b) cele de citire cnd nu exist cititori activi + cele de scriere.


ncercrile din prima categorie trebuie satisfcute necondiionat.
Vom considera ncercrile de deschidere a crii din a doua categorie ca fiind egal rivale;
drept urmare vom controla executarea lor printr-un semafor sem ce trebuie iniializat cu 1, deoarece
orice prim ncercare de acest tip trebuie validat. Din motive de simetrie, sem va fi actualizat (prin
operaia signal) n exact aceleai situaii. Semaforul sem va avea valoarea 0 dac i numai dac la
carte are acces cel puin o persoan.
Semafoarele ExRec i s sunt folosite pentru excludere reciproc, deci sunt iniializate cu 1.
Procesele Cititori :
repeat
deschide(i,citire); preia informaia dorit
inchide(i,citire); prelucreaz informaia citit
until false

Procesele Scriitori :
repeat

redacteaz un text nou;


introdu textul in carte;

deschide(i,scriere);
inchide(i,scriere);

until false
procedure deschide(i,caz)
if caz=citire
then wait(ExRec);
if nrcit=0 then wait(sem);
wait(s); write(" C" + i + "("); signal(s);
nrcit++;
signal(ExRec);
else wait(sem);
wait(s); write((" S" + i + "("); signal(s);
end
procedure inchide(i,caz)
if caz=citire
then wait(ExRec);
wait(s); write(" C" + i + ")"); signal(s);
nrcit--;
if nrcit=0 then signal(sem);
signal(ExRec);
else wait(s); write(" S" + i + ")"); signal(s);
signal(sem)
end

Aciunile de deschidere i nchidere pentru citire trebuie executate sub excludere reciproc.
ntr-adevr, la apelul inchide(i,citire) cu nrcit=1, dup decrementarea lui nrcit nu este
sigur c nrcit=0, deoarece este posibil ca ntre timp valoarea "resursei comune" nrcit s se fi
modificat printr-un apel deschide(j,citire). De aceea am folosit semaforul ExRec, iniializat
cu 1.
Observaie. Dac am include "pentru siguran" i operaia de nchidere pentru scriere ntre
wait(ExRec) i signal(ExRec), se poate ajunge la blocare global n urmtoarea situaie:
- scriitorul 1 deschide cartea; sem i ExRec au acum valorile 0, respectiv 1;
- cititorul 1 trece de semaforul ExRec i se blocheaz la sem; acum ExRec=0;
- n continuare, orice alt proces (n particular scriitorul 1) se va bloca la ExRec.
12

Notm prin nrscr numrul de scriitori activi. n cele ce urmeaz vom nelege prin stare
a programului tripletul (nrscr,nrcit;sem). Starea iniial este (0,0;1).
Vom arta c singurele stri posibile sunt urmtoarele:
(0,0;1), (1,0;0) i (0,n;0) cu n>0.
Dac suntem n starea (0,0;1):
- poate avea loc o deschidere pentru citire sau scriere, ajungndu-se n (0,1;0) sau (1,0;0);
- nu poate avea loc o nchidere, deoarece forma proceselor presupune c o nchidere este
precedat de o deschidere, n contradicie cu nrscr=nrcit=0.
Dac suntem n starea (1,0;0), atunci:
- poate avea loc o nchidere pentru scriere i se trece n starea (0,0;1) (dac nu exist procese
blocate la semaforul sem) sau n una din strile (1,0;0), (0,1;0) (dac exist procese blocate la
semaforul sem);
- nu poate avea loc o deschidere pentru scriere deoarece sem=0;
- nu poate avea loc o deschidere pentru citire deoarece nrcit=0 i sem=0;
- nu poate avea loc o nchidere pentru citire deoarece nrcit=0.
Dac suntem n starea (0,n;0) cu n>0:
- poate avea loc o deschidere pentru citire, trecndu-se n starea (0,n+1;0);
- nu poate avea loc o deschidere pentru scriere, deoarece sem=0;
- nu poate avea loc o nchidere pentru scriere, deoarece nrscr=0;
- dac n>1, printr-o nchidere pentru citire se trece n starea (0,n-1;0);
- dac nrcit=1, atunci printr-o nchidere pentru citire se trece n starea (0,0;1) (dac nu
exist procese blocate la semaforul sem) sau n una din strile (1,0;0), (0,1;0) (dac exist
procese blocate la semaforul sem).
Din analiza de mai sus rezult validitatea procedurilor deschide i inchide, precum i
faptul c sem este un semafor binar.
Exemplul 4. Prezentm programul Java corespunztor problemei Cititori - Scriitori:
import java.util.concurrent.*;
class CitScr {
public static void main(String[] sss) {
Cit[] cititori = new Cit[5];
Scr[] scriitori = new Scr[3];
for(int i=0; i<5; i++) cititori[i] = new Cit(i);
for(int i=0; i<3; i++) scriitori[i] = new Scr(i);
for(int i=0; i<5; i++) cititori[i].start();
for(int i=0; i<3; i++) scriitori[i].start();
}
}
class C {
static int nrcit;
static Semaphore ExRec = new Semaphore(1,true),
sem
= new Semaphore(1,true);
static void delay(int i) throws InterruptedException {
Thread.sleep( (int) (i * Math.random()) );
}
static void deschide(int i, String acces)
13

throws InterruptedException {
if(acces.equals("citire")) {
ExRec.acquire();
if(nrcit==0) sem.acquire();
System.out.print("\tC" + i + "("); nrcit++;
ExRec.release();
}
else {
sem.acquire();
System.out.print("\tS" + i + "(");
}
}
static void inchide(int i, String acces)
throws InterruptedException {
if(acces.equals("citire")) {
ExRec.acquire();
System.out.print("\tC" + i + ")");
nrcit--; if(nrcit==0) sem.release();
ExRec.release();
}
else {
System.out.print("\tS" + i + ")");
sem.release();
}
}
}
class Cit extends Thread {
int i;
Cit(int i) { this.i = i; }
public void run() {
try {
for(int k=0; k<5; k++) {
C.delay(50); C.deschide(i,"citire");
C.delay(100); C.inchide(i,"citire");
}
}
catch(Exception e) { }
}
}
class Scr extends Thread {
int i;
Scr(int i) { this.i = i; }
public void run() {
try {
for(int k=0; k<3; k++) {
C.delay(50); C.deschide(i,"scriere");
C.delay(100); C.inchide(i,"scriere");
}
}
catch(Exception e) { }
}
}

Observaie. Metodele tryAcquire (cu cerere de permisiune unic sau multipl) au cte
14

o variant pentru timp real.

Bariere
La analiza Jocului vieii am vzut utilitatea unui semafor avnd totdeauna valoarea zero,
numit i barier: mai multe fire se ateapt unul pe altul s ndeplineasc o prim aciune, nainte
de a trece mai departe.
Prezentm n continuare o facilitate mai complex oferit de ava pentru lucrul cu bariere.
Clasa CyclicBarrier apare n pachetul java.util.concurrent. Sunt disponibili
doi constructori:
CyclicBarrier(int n)
CyclicBarrier(int n, Runnable fir)

unde n reprezint numrul de fire care sunt ateptate s ajung la barier. Pentru a doua form,
aciunea precizat pentru fir va fi executat dup ce toate cele n fire au ajuns la barier i nainte
ca ele s-i reia executarea.
Bariera se numete ciclic deoarece poate fi refolosit dup ce toate firele au ajuns la
barier, punct n care i reiau executarea.
Dintre metodele clasei menionm doar urmtoarea:
public int await() throws InterruptedException, BrokenBarrierException

firul curent este ataat barierei care execut aceast metod i trece n ateptare pn cnd
toate cele n fire au ajuns la barier, relundu-i apoi executarea. Rezultatul ntors este k = al
ctelea a ajuns firul la barier, cu precizarea c 0 corespunde ultimului fir ajuns la barier.
Exemplul 5. Sunt lansate 5 fire, cu identitile 0,1,...,4, care execut urmtoarele
aciuni:
- ntr-o prim etap i tipresc de 10 ori identitatea; cnd toate au ajuns la barier, se
execut firul fir, care tiprete "****";
- cele 5 fire i reiau activitatea i pentru fiecare este afiat al ctelea a ajuns la barier;
n a doua etap se reiau activitile de mai sus, cu deosebirea c ele i tipresc de 10 ori
identitatea incrementat cu 10.
Este creat o barier cb de tipul CyclicBarrier, atandu-i-se firul fir. Bariera este
folosit pentru realizarea primei etape, iar apoi refolosit pentru realizarea celei de a doua etape; n
acest scop este necesar ca firele s invoce nc o dat metoda await prin intermediul barierei.
import java.util.*; import java.util.concurrent.*;
class Tip extends Thread {
int i;
static Runnable fir =
new Runnable() {
public void run() {
try { Thread.sleep(500); }
catch(InterruptedException ie) { }
System.out.println("****");
}
};
static CyclicBarrier cb = new CyclicBarrier(5,fir);
Tip(int ii) { i = ii; }
public void run() {
Random r = new Random();
15

try {
for(int j=0; j<10; j++) {
sleep(10+r.nextInt(15)); System.out.print(i + "\t");
}
System.out.println("!!!!" + i + ":" + cb.await() + "!!!!");
for(int j=0; j<10; j++) {
sleep(10+r.nextInt(15)); System.out.print((i+10) + "\t");
}
System.out.println("!!!!" + i + ":" + cb.await() + "!!!!");
}
catch(Exception e) { }
}
}
class Bariera {
public static void main(String[] sss) throws Exception {
for(int i=0; i<5; i++) new Tip(i).start();
}
}

16

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