problema sectiunii criticesoftware.ucv.ro/~cbadica/scd/cap2.pdf2018 - 2019 • problema sectiunii...
TRANSCRIPT
2018 - 2019
Problema sectiunii critice
Capitolul 2
2018 - 2019
Rezumat
• Mecanisme de sincronizare pentru sectiunea critica.
Analiza unor algoritmi de baza.
• Implementarea mecansimelor de sincronizare in
Promela si Java.
2018 - 2019
Definirea problemei sectiunii critice
• Se considera N 2 procese care executa fiecare cate o
bucla infinita. Corpul buclei cuprinde:
– (i) sectiunea critica
– (ii) sectiunea non-critica.
• Cerintele de corectitudine: ce inseamna “sectiune
critica”?
• Cerinta de eficienta: este accesul la sectiunea critica
eficient?
• Presupuneri: referitoare la sectiunile critica si non-
critica.
2018 - 2019
Vizualizarea problemei sectiunii critice
2018 - 2019
Cerinte 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
2018 - 2019
Cerinte de eficienta
• Accesul la sectiunea critica trebuie sa consume
cat mai putine resurse:
– timp de executie
– memorie
2018 - 2019
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.
2018 - 2019
• Problema sectiunii critice se rezolva printr-un mecansim de sincronizare. Acesta include secvente de instructiuni inainte si dupa sectiunea critica:
– Pre-protocol
– Post-protocol
Algoritm generic pentru problema sectiunii critice
variabile globale
p q
variabile locale
loop forever
sectiune non-critica
pre-protocol
sectiune critica
post-protocol
variabile locale
loop forever
sectiune non-critica
pre-protocol
sectiune critica
post-protocol
Algoritm generic
Sursa: M.Ben-Ari, 2006
2018 - 2019
• 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.
Sectiune critica V1
integer turn 1
p q
loop forever
p1: sectiune non-critica
p2: await turn = 1
p3: sectiune critica
p4: turn 2
loop forever
q1: sectiune non-critica
q2: await turn = 2
q3: sectiune critica
q4: turn 1
Prima incercare
Sursa: M.Ben-Ari, 2006
2018 - 2019
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 curenta
este reprezentata prin tripletul:
⟨𝑝𝑐𝑝, 𝑝𝑐𝑞 , 𝑡𝑢𝑟𝑛⟩
2018 - 2019
Numarul de stari
• 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.
2018 - 2019
Vizualizarea diagramelor de stari
Sursa: M.Ben-Ari, 2006
2018 - 2019
Excluderea mutuala in prima incercare
• Rezulta din diagrama de stari simplificata 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.
2018 - 2019
Lipsa interblocajului in prima incercare
• Pe diagrama simplificata, fiind simetrica, analizam 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.
2018 - 2019
Lipsa infometarii
• 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.
Sursa: M.Ben-Ari, 2006
2018 - 2019
Sectiune critica V2
boolean wantp false
boolean wantq false
p q
loop forever
p1: sectiune non-critica
p2: await wantq = false
p3: wantp true
p4: sectiune critica
p5: wantp false
loop forever
q1: sectiune non-critica
q2: await wantp = false
q3: wantq true
q4: sectiune critica
q5: wantq false
A doua incercare
Sursa: M.Ben-Ari, 2006
𝑤𝑎𝑛𝑡𝑝 ↔ p doreste sa intre in sectiunea critica
2018 - 2019
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.
2018 - 2019
Sectiune critica V3
boolean wantp false
boolean wantq false
p q
loop forever
p1: sectiune non-critica
p2: wantp true
p3: await wantq = false
p4: sectiune critica
p5: wantp false
loop forever
q1: sectiune non-critica
q2: wantq true
q3: await wantp = false
q4: sectiune critica
q5: wantq false
A treia incercare
Sursa: M.Ben-Ari, 2006 • Tema: Sa se arate ca acest algoritm:
– Verifica proprietatea de excludere mutuala
– Incalca proprietatea de interblocare
2018 - 2019
Sectiune critica V4
boolean wantp false
boolean wantq false
p q
loop forever
p1: sectiune non-critica
p2: wantp true
p3: while wantq = true
p4: wantp false
p5 wantp true
p6: sectiune critica
p7: wantp false
loop forever
q1: sectiune non-critica
q2: wantq true
q3: while wantp = true
q4: wantq false
q5 wantq true
q6: sectiune critica
q7: wantq false
A patra incercare
Sursa: M.Ben-Ari, 2006 • Tema: Sa se arate ca acest algoritm:
– 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.
2018 - 2019
Algoritmul lui Dekker
boolean wantp false
boolean wantq false
interger turn 1
p q
loop forever
p1: sectiune non-critica
p2: wantp true
p3: while wantq = true
p4: if turn = 2
p5: wantp false
p6: await turn = 1
p7: wantp true
p8: sectiune critica
p9: turn 2
p10: wantp false
loop forever
q1: sectiune non-critica
q2: wantq true
q3: while wantp = true
q4: if turn = 1
q5: wantq false
q6: await turn = 2
q7: wantq true
q8: sectiune critica
q9: turn 1
q10: wantq false
Algoritmul lui Decker
Sursa: M.Ben-Ari, 2006
2018 - 2019
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
2018 - 2019
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(common, local) is:
integer temp
temp common
common local
local temp
Realizeaza citirea valorii common
si in acelasi timp setarea ei la valoarea
local intr-o secventa atomica.
2018 - 2019
Sectiune critica cu test-and-set
boolean common false
p q
integer localp
loop forever
p1: sectiune non-critica
repeat
p2: test-and-set(common,localp)
p3: until localp = false
p4: sectiune critica
p5: common false
integer localq
loop forever
q1: sectiune non-critica
repeat
q2: test-and-set(common,localq)
q3: until localq = false
q4: sectiune critica
q5: common false
Sectiune critica cu test-and-set
Sursa: M.Ben-Ari, 2006
• Tema: Sa se arate ca acest algoritm verifica toate cerintele de
corectitudine ale problemei sectiunii critice.
2018 - 2019
Sectiune critica cu exchange
boolean common true
p q
integer localp = false
loop forever
p1: sectiune non-critica
repeat
p2: exchange(common,localp)
p3: until localp = true
p4: sectiune critica
p5: exchange(common, localp)
integer localq = false
loop forever
q1: sectiune non-critica
repeat
q2: exchange(common,localq)
q3: until localq = true
q4: sectiune critica
q5: exchange(common, localq)
Sectiune critica cu exchange
Sursa: M.Ben-Ari, 2006
• Tema: Sa se arate ca acest algoritm verifica toate cerintele de
corectitudine ale problemei sectiunii critice.
2018 - 2019
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.
2018 - 2019
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.
2018 - 2019
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.
2018 - 2019
Folosirea interfetei Lock
• Pentru sincronizare se defineste un obiect ce implementeaza interfata Lock.
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.
Aceasta clasa implementeaza algoritmi
pentru sectiunea critica.
2018 - 2019
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() {
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;
}
}
}
public void unlock() {
int i = ThreadID.get();
turn = 1-i;
want[i] = false;
}
Se foloseste clasa ThreadID din Herlihy
& Shavit, 2008, 2012
Variabile volatile
2018 - 2019
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();
}
}
}
2018 - 2019
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
2018 - 2019
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 de intializare a
obiectului.
• Citirea valorii obiectului se face cu get. Daca obiectul nu a fost creat, el
este creat si initializat apeland initialValue. 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
necesara in 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.
2018 - 2019
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++;
}
}
}
2018 - 2019
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.
2018 - 2019
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.
2018 - 2019
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.
2018 - 2019
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.
2018 - 2019
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.
2018 - 2019
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.
2018 - 2019
Tema 3
• Algoritmul lui Petereson.
Sursa: M.Ben-Ari, 2006 - Modelare si analiza in Promela
- Implementare in Java folosind interfata Lock