Sunteți pe pagina 1din 35

Problema sectiunii critice

Capitolul 2

2017 - 2018
Rezumat

• Mecanisme de sincronizare pentru sectiunea critica.


Analiza unor algoritmi de baza.

• Implementarea mecansimelor de sincronizare in


Promela si Java.

2017 - 2018
Definirea problemei sectiunii critice
• Se considera N  2 procese care executa fiecare cate o bucla infinita.
Corpul buclei cuprinde: (i) sectiunea critica si (ii) sectiunea non-critica.
• Cerintele de corectitudine:
– Excludere mutuala. Instructiunile din sectiunile critice a doua sau mai multe procese
nu pot fi intretesute.
– Lipsa interblocaj (engl. deadlock). Daca anumite procese incearca sa intre in
sectiunea lor critica atunci eventual unul dintre ele va reusi.
– Lipsa infometare individuala (engl. starvation). Orice proces care incearca sa intre in
sectiunea sa critica eventual va reusi.
• Observatie: Lipsa infometarii individuale  lipsa interblocajului
• Cerinta de eficienta:
– Accesul la sectiunea critica trebuie sa consume cat mai putin timp si memorie
• Presupuneri:
– Progres in sectiunea critica. Daca un proces intra in sectiunea critica, el va iesi din ea
– Non-progres in sectiunea non-critica. Daca un proces este in sectiunea non-critica el
se poate termina sau se poate bucla la infinit.

2017 - 2018
Vizualizarea problemei sectiunii critice

2017 - 2018
Algoritm generic pentru problema sectiunii critice
Algoritm generic pentru problema sectiunii critice
variabile globale
p q
variabile locale variabile locale
loop forever loop forever
sectiune non-critica sectiune non-critica
pre-protocol pre-protocol
sectiune critica sectiune critica
post-protocol post-protocol
Sursa: M.Ben-Ari, 2006

• Problema sectiunii critice se rezolva printr-un mecansim de sincronizare.


Acesta include secvente de instructiuni inainte si dupa sectiunea critica:
– Pre-protocol
– Post-protocol 2017 - 2018
Prima incercare
Sectiune critica V1
integer turn  1
p q
loop forever loop forever
p1: sectiune non-critica q1: sectiune non-critica
p2: await turn = 1 q2: await turn = 2
p3: sectiune critica q3: sectiune critica
p4: turn  2 q4: turn  1
Sursa: M.Ben-Ari, 2006

• await conditie este o instructiune de asteptare conditionala. Daca


conditie este falsa atunci executia procesului ce o contine se blocheaza
pana cand conditie devine adevarata, prin actiunea altui proces. Altfel
executia procesului ce o contine continua.
2017 - 2018
Diagrama de stari
• Urmatorul pas al unui algoritm concurent este determinat de starea
curenta a calculului, ce cuprinde:
– valorile pointerilor de control
– valorile variabilelor globale si locale ale fiecarui proces
• Pentru algoritmul “Sectiune critica V1” starea este data de tripletul pcp
pcq, turn.
• Daca algoritmul contine 𝑁 procese, fiecare proces i are ni instructiuni,
exista 𝑀 variabile si fiecare variabila 𝑗 are 𝑚𝑗 valori posibile atunci
numarul maxim de stari posibile este:
𝑛1 × 𝑛2 × ⋯ × 𝑛𝑁 × 𝑚1 × 𝑚2 × ⋯ × 𝑚𝑀
• Pentru algoritmul nostru acest numar este 4 × 4 × 2 = 32 stari posibile
din care numai 16 pot fi atinse din starea initiala 〈𝑝1, 𝑞1,1〉.
• Numarul de stari se poate reduce la 4 daca se renunta la instructiunile din
sectiunea critica si non-critica, pe baza observatiei ca p1 si p3 se pot
inlocui cu comentarii, p1 este totuna cu p2, iar p3 este totuna cu p4.
2017 - 2018
Vizualizarea diagramelor de stari

Sursa: M.Ben-Ari, 2006

2017 - 2018
Corectitudinea primei incercari

• Excluderea mutuala rezulta analizand diagrama de stari


simplificata si observand ca starile p2,q2,1 si p2,q2,2 nu
sunt posibile. Cu alte cuvinte toate starile de forma p2,q2,turn
sunt imposibile pentru orice valoare posibila a variabilei turn.

• Lipsa interblocajului se demonstreaza pe diagrama


simplificata. Fiind simetrica, se considera doar starile din
stanga:
– Din starea p1,q1,2, q va intra in sectiunea critica
– Din starea p1,q2,2, q fiind in sectiunea critica, va progresa (conform
ipotezei de progres in sectiunea critica) in starea p1,q1,1, de unde p
va intra in sectiunea critica.

2017 - 2018
Lipsa infometarii

Sursa: M.Ben-Ari, 2006

• Lipsa infometarii se analizeaza pe diagrama initiala. Sa


presupunem ca algoritmul se afla in starea p2,q1,2. p doreste
sa intre in sectiunea critica, iar q se afla in sectiunea non-
critica. Conform presupunerii de non-progres in sectiunea non-
critica, turn nu va putea deveni 1, astfel ca p este infometat.
2017 - 2018
A doua incercare

Sectiune critica V2
boolean wantp  false
boolean wantq  false
p q
loop forever loop forever
p1: sectiune non-critica q1: sectiune non-critica
p2: await wantq = false q2: await wantp = false
p3: wantp  true q3: wantq  true
p4: sectiune critica q4: sectiune critica
p5: wantp  false q5: wantq  false

Sursa: M.Ben-Ari, 2006

2017 - 2018
A doua incercare incalca excluderea mutuala

• 4 = p2, 11 = q2, 5 = p3, 12 = q3, 6 =


p5, 13 = q5.
• Starea este  pcp,pcq,wantp,wantq
• Se obtine secventa de stari 4,11,0,0 
4,12,0,0  5,12,0,0  5,13,0,1 
6,13,1,1 unde wantp = wantq = true.
2017 - 2018
A treia incercare
Sectiune critica V3
boolean wantp  false
boolean wantq  false
p q
loop forever loop forever
p1: sectiune non-critica q1: sectiune non-critica
p2: wantp  true q2: wantq  true
p3: await wantq = false q3: await wantp = false
p4: sectiune critica q4: sectiune critica
p5: wantp  false q5: wantq  false
Sursa: M.Ben-Ari, 2006
• Tema: Sa se arate ca acest algoritm:
– Verifica proprietatea de excludere mutuala
– Incalca proprietatea de interblocare

2017 - 2018
A patra incercare
Sectiune critica V4
boolean wantp  false
boolean wantq  false
p q
loop forever loop forever
p1: sectiune non-critica q1: sectiune non-critica
p2: wantp  true q2: wantq  true
p3: while wantq = true q3: while wantp = true
p4: wantp  false q4: wantq  false
p5 wantp  true q5 wantq  true
p6: sectiune critica q6: sectiune critica
p7: wantp  false q7: wantq  false

• Tema: Sa se arate ca acest algoritm: Sursa: M.Ben-Ari, 2006


– Verifica proprietatea de excludere mutuala
– Nu verifica proprietatea de interblocare. Apare insa livelock = se executa instructiuni
dar nu exista progres in sensul de a se intra in sectiunea critica
– Incalca proprietatea de infometare. 2017 - 2018
Algoritmul lui Decker
Algoritmul lui Dekker
boolean wantp  false
boolean wantq  false
interger turn  1
p q
loop forever loop forever
p1: sectiune non-critica q1: sectiune non-critica
p2: wantp  true q2: wantq  true
p3: while wantq = true q3: while wantp = true
p4: if turn = 2 q4: if turn = 1
p5: wantp  false q5: wantq  false
p6: await turn = 1 q6: await turn = 2
p7: wantp  true q7: wantq  true
p8: sectiune critica q8: sectiune critica
p9: turn  2 q9: turn  1
p10: wantp  false q10: wantq  false

Sursa: M.Ben-Ari, 2006


2017 - 2018
Corectitudinea algoritmului lui Dekker

• Observatie: dreptul de a insista sa intre in


sectiunea critica este controlat prin variabila
turn.
• Tema: Sa se arate ca algoritmul lui Dekker:
– Verifica proprietatea de excludere mutuala
– Verifica proprietatea de interblocare
– Verifica proprietatea de infometare

2017 - 2018
Instructiuni atomice complexe
• Este dificil de rezolvat problema sectiunii critice folosind doar
instructiuni atomice de incarcare si stocare din/in memorie
(engl. load and store).
• Instructiunea atomica test-and-set incarca valoarea unei
variabile booleene common si apoi o seteaza la valoarea “true”.
test-and-set(common, local) is:
local  common
common  true
• Alt exemplu de instructiune atomica este exchange:
exchange(a, b) is:
integer temp
temp  a
ab
b  temp 2017 - 2018
Sectiune critica cu test-and-set
Sectiune critica cu test-and-set
boolean common  false
p q
integer localp integer localq
loop forever loop forever
p1: sectiune non-critica q1: sectiune non-critica
repeat repeat
p2: test-and-set(common,localp) q2: test-and-set(common,localq)
p3: until localp = false q3: until localq = false
p4: sectiune critica q4: sectiune critica
p5: common  false q5: common  false

Sursa: M.Ben-Ari, 2006

• Tema: Sa se arate ca acest algoritm verifica toate cerintele de


corectitudine ale problemei sectiunii critice.
2017 - 2018
Sectiune critica cu exchange
Sectiune critica cu exchange
boolean common  true
p q
integer localp = false integer localq = false
loop forever loop forever
p1: sectiune non-critica q1: sectiune non-critica
repeat repeat
p2: exchange(common,localp) q2: exchange(common,localq)
p3: until localp = false q3: until localq = true
p4: sectiune critica q4: sectiune critica
p5: exchange(common, localp) q5: exchange(common, localq)

Sursa: M.Ben-Ari, 2006

• Tema: Sa se arate ca acest algoritm verifica toate cerintele de


corectitudine ale problemei sectiunii critice.
2017 - 2018
Alte instructiuni atomice complexe
fetch-and-add(common, local, x) is:
local  common
common  common + x

compare-and-swap(common, old, new) is:


integer temp
temp  common
if common = old
common  new
return temp

• Tema: Sa se rezolve problema sectiunii critice folosind separat


instructiunile atomice fetch-and-add si compare-and-swap.
2017 - 2018
Instructiuni atomice in Promela
• Toate intructiunile in Promela sunt atomice. Daca se doreste
simularea efectului generarii de cod, trebuie transpus in
Promela exact codul rezultat in urma compilarii.
• Daca se doreste ca o secventa de instructiuni sa fie considerata
atomica (de exemplu pentru implementarea instructiunilor de
tip test-and-set) se va folosi instructiunea atomic.
bit common = 0;
inline test-and-set (local) {
atomic {
local = common;
common = 1
}
}
• Tema: Sa se implementeze sectiunea critica in Promela
folosind instructiuni atomice complexe.
2017 - 2018
Interfata Lock in Java
• Un mecansim de sincronizare se poate implementa folosind
interfata java.util.concurrent.locks.Lock in Java.

public interface Lock {


public void lock(); // before entering critical section
public void unlock(); // before leaving critical section
}

• Metoda lock reprezinta codul care se executa inainte de a intra


in sectiunea critica.
• Metoda unlock reprezinta codul care se executa dupa iesirea din
sectiunea critica.
2017 - 2018
Folosirea interfetei Lock
• Pentru sincronizare se defineste un obiect ce implementeaza interfata Lock.
Aceasta clasa implementeaza algoritmi
pentru sectiunea critica.
public class Counter {
private long value = 0;
private Lock mutex = new LockImplementation();

public long getAndIncrement() {


long temp;
mutex.lock(); // enter critical section
try {
temp = value; // in critical section
value = temp + 1; // in critical section
} finally {
mutex.unlock(); // leave critical section
}
return temp;
}
}

• Metoda lock reprezinta codul care se executa inainte de a intra in sectiunea critica.
• Metoda unlock reprezinta codul care se executa dupa iesirea din sectiunea critica.
2017 - 2018
Algoritmul lui Dekker in Java
import java.util.concurrent.locks.Lock;
public class LockDekker implements Lock {
private volatile boolean[] want = {false, false};
private volatile int turn = 0;
public void lock() {
Variabile volatile
int i = ThreadID.get();
int j = 1-i;
want[i] = true;
while (want[j]) {
if (turn == j) {
want[i] = false;
while (turn != i) {}
want[i] = true;
}
}
} Se foloseste clasa ThreadID din Herlihy
public void unlock() { & Shavit, 2008, 2012
int i = ThreadID.get();
turn = 1-i;
want[i] = false;
}
2017 - 2018
Program de test
public class LockTest {
final static int THREADS = 2;
final static int COUNT = 1024;
final static int PER_THREAD = COUNT / THREADS;
final static Thread[] thread = new Thread[THREADS];
static Counter c = new Counter();
public static void main(String[] args) {
ThreadID.reset();
for (int i = 0; i < THREADS; i++) { thread[i] = new CounterThread();}
for (int i = 0; i < THREADS; i++) { thread[i].start();}
for (int i = 0; i < THREADS; i++) {
try { thread[i].join(); }
catch(InterruptedException e) {}
}
System.out.println("Counter is: "+c.getValue());
}
}
class CounterThread extends Thread {
public void run() {
for (int i = 0; i < LockTest.PER_THREAD; i++) {
LockTest.c.getAndIncrement();
}
}
} 2017 - 2018
Variabile locale in fire - I
• Firele pot defini variabile locale private cu domeniu de acces
firul respectiv (engl. thread scope). Acest domeniu se numeste
si domeniul local de acces al firului – Thread Local Scope.
• Orice fir poate defini unul sau mai multe obiecte in TLS, iar
aceste obiecte sunt atat locale, cat si globale pentru firul
respectiv care le poate accesa.
– Globale – pot fi accesate de oriunde din cadrul firului respectiv. De
exemplu, daca un fir apeleaza o metoda dintr-un obiect, apelul metodei
va putea accesa variabilele TLS.
– Locale – fiecare fir va avea acces numai la propriile variable locale
TLS, variabilele TLS din alte fire fiind inaccesibile

2017 - 2018
Variabile locale in fire - II
public class ThreadLocal<T> {
protected T initialValue( );
public T get( );
public void set(T value);
public void remove( );
}
• Metoda initialValue se suprascrie pentru a returna valoarea folosita pentru
intializarea obiectului.
• Citirea valorii obiectului se face cu get. Daca valoarea curenta este stearsa
cu remove, o citire ulterioara cu get reapeleaza metoda initialValue pentru
initializare.
• Rescrierea valorii obiectului se face cu set, aceasta metoda nefiind
necesarain majoritatea cazurilor.
• Obiectele TLS se declara in general cu static, fiind astfel partajate de toate
firele. Cand se apeleaza metoda get, mecanismul intern al clasei
ThreadLocal asigura returnarea obiectului asignat unui fir specific
2017 - 2018
Clasa ThreadID
public class ThreadID {
private static volatile int nextID = 0;
private static ThreadLocalID threadID = new ThreadLocalID();
public static int get() {
return threadID.get();
}
public static void reset() {
nextID = 0;
}
private static class ThreadLocalID
extends ThreadLocal<Integer> {
protected synchronized Integer initialValue() {
return nextID++;
}
}
}

2017 - 2018
Instructiuni atomice in Java
• Pachetul java.util.concurrent.atomic contine o multime de clase
utile pentru implementarea unor variabile cu acces atomic.
• Aceste clase dispun de metode get si set ce lucreaza similar cu
citirea respectiv scrierea variabilelor definite cu volatile. O
operatie set defineste o relatie de precedenta a executiei fata de
orice operatie ulterioara get asupra unei variabile.
• Spre exemplu, folosind clasa AtomicInteger se poate
implementa un contor cu acces atomic la operatiile de
incrementare / decrementare.
• Tema: Sa se reimplementeze programul prezentat anterior
pentru o multime de fire care realizeaza o operatie de numarare
concurenta, folosind clasa AtomicInteger.
2017 - 2018
Cuvantul cheie volatile in Java - I
• volatile in Java indica in principal faptul ca variabilele trebuie
stocate in memoria principala, nu in registre sau in memoria cache
pentru optimizarea codului.
• Incepand cu Java 5, volatile asigura mai multe garantii necesare
programarii concurente:
– Garantia de asigurare a vizibilitatii actualizarii variabilelor partajate de
mai multe fire. Prin declararea unei variabile ca volatile se
garanteaza ca orice scriere a sa va fi realizata direct in memoria
principala, fiind corect vizibila din alte fire ce o citesc ulterior.
– Garantia unor relatii de precedenta intre instructiuni.
• Daca firul 1 scrie o variabila volatile V, iar firul 2 citeste ulterior variabila V,
atunci toate variabilele vizibile in firul 1 inaintea scrierii variabilei V vor fi
vizibile in firul 2. Aceasta inseamna ca odata cu scrierea in memorie a lui V, se
scriu in memorie si celelalte variabile modificate de firul 1.
• Instructiunile de acces la variabilele volatile nu sunt reordonate pentru
cresterea performantei executiei. 2017 - 2018
Cuvantul cheie volatile in Java - II
• Folosirea volatile nu este suficienta pentru folosirea /
accesarea variabilelor partajate de mai multe fire.
• volatile este suficient atunci cand valoarea scrisa de un fir
pentru o variabila este independenta de valoarea precedenta a
variabilei. Altfel, daca valoarea trebuie intai citita, iar valoarea
urmatoare determinata pe baza valorii citite, este necesara
folosirea sectiunilor critice pentru accesul concurent la variabila
din mai multe fire.
• Citirea si scrierea variabilelor volatile este mai costisitoare
decat citirea si scrierea variabilelor ne-volatile deoarece:
– variabilele volatile se citesc din si se scriu in memoria principala,
in timp ce variabilele ne-volatile se pot accesa din memoria cache.
– variabilele volatile previn reordonarea instructiunilor pentru
cresterea perfomantei. 2017 - 2018
Tema 1
• O sarcina (task sau job) cere determinarea daca un numar natural
𝑖 ∈ ℕ este prim. Considerand ca 𝑖 poate lua toate valorile din intervalul
[1, 𝑛], problema poate fi privita astfel: cum sa alocam cat mai eficient
executia celor n sarcini pe cele 𝑘 fire disponibile ?
• Variantele initiale, propuse ca tema in cursul trecut, sugereaza
folosirea unei prealocari (sau alocare statica) a sarcinilor pe firele
disponibile, astfel incat incarcarea firelor sa fie cat mai echilibrata.
• Se poate imagina o varianta de alocare dinamica, in sensul ca orice fir
liber (la inceputul executiei sau la terminarea executei sarcinii curente)
isi alege sarcina urmatoare disponibila. In acest fel este asigurat
echilibrul incarcarii tuturor firelor.
• Urmatoarea sarcina disponibila se poate reprezenta printr-un
numarator partajat (shared counter) de toate firele. Ori de cate ori este
preluata urmatoarea sarcina disponibila, numaratorul trebuie
incrementat. Accesul la numarator este o sectiune critica. 2017 - 2018
Tema 2
• Problema prizonierilor. Se considera o multime de 𝑝 prizonieri.
Directorul le ofera urmatoarea sansa:
– Se permite prizonierilor sa se intalneasca toti o singura data, in prima zi
– In zilele urmatoare ei sunt inchisi in camere separate, un singur prizonier pe
camera
– Se considera o camera cu un comutator cu doua pozitii: aprins si stins
– Directorul selecteaza in fiecare zi cate un prizonier la intamplare si il duce in
camera cu comutatorul. Prizonierul poate actiona comutatorul schimbandu-i
pozitia sau il poate lasa neschimbat. Nimeni nu intra in camera comutatorului
in afara prizonierilor.
– Selectia prizonierilor este echitabila. Aceasta inseamna ca fiecare prizonier va
vizita eventual camera comutatorului de oricat de multe ori, in sensul ca nu
exista posibilitatea ca de la un moment dat incolo el sa fie ignorat de director.
– In orice moment un prizonier poate declara: “toti prizonierii au vizitat camera
comuntatorului”. Daca aceasta afirmatie este adevarata, prizonierii sunt
eliberati, altfel raman in inchisoare.
2017 - 2018
Tema 2 - II
a. Sa se propuna o strategie castigatoare a prizonierilor stiind ca
initial comutatorul este stins.
b. Sa se propuna o strategie castigatoare a prizonierilor nestiind
starea initiala a comutatorului.
c. Sa se implementeze un program Java care simuleaza alegerea
aleatoare a prizonierilor de catre director si trecerea lor prin
camera cu comutatorul. Camera se va implementa folosind
tehnica “sectiunii critice”, iar fiecare prizonier se va simula
printr-un fir separat ce acceseaza camera. In cadrul fiecarui fir
sa se implementeze strategia de rationament a prizonierilor si
sa se verifice corectitudinea strategiei prin executii multiple ale
simularii, in ambele situatii de la punctele a si b.

2017 - 2018
Tema 3
• Algoritmul lui Petereson.

Sursa: M.Ben-Ari, 2006


- Modelare si analiza in Promela
- Implementare in Java folosind interfata Lock
2017 - 2018

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