Documente Academic
Documente Profesional
Documente Cultură
Cap8 PDF
Cap8 PDF
Capitolul 8
2017-2018
Abordarea naiva
2017-2018
Folosirea zavoarelor
• Se folosesc clase care implementeaza interfata
Lock din pachetul java.util.concurrent.locks.
import java.util.concurrent.locks.*;
Lock lock = new ReentrantLock(); Declaratii
// ...
lock.lock();
try {
// ... Cod sectiune critica Sectiune critica
} finally {
lock.unlock();
}
2017-2018
Alte abordari
2017-2018
Sincronizare fina
• Obiectul se partitioneaza intr-o multime de
componeente.
2017-2018
Multimi abstracte
public interface Set<T> {
boolean add(T x);
boolean remove(T x);
boolean contains(T x);
}
• add(x) adauga x la multime si intoarce true dnd x nu
era in multime.
• remove(x) elimina x din multime intorcand true dnd x
era in multime.
• contains(x) realizeaza un test de apartenenta, intoarce
true dnd x se afla in multime.
2017-2018
Implementarea multimilor cu liste I
• Vom folosi listele inlantuite pentru a implementa multimile sub
forma unor colectii de elemente care nu contin duplicate.
private class Node {
T item;
int key;
Node next;
Node(T item) {
this.item = item;
this.key = item.hashCode();
}
}
• Fiecarui element ii corespunde un nod in lista.
• item memoreaza valoarea elementului
• key este un index (engl. hash code) cu valoare unica pentru fiecare
nod. Se considera ca elementele listei sunt ordonate crescator
conform ordinii definite pentru acest camp. 2017-2018
Implementarea multimilor cu liste II
• Se presupune ca orice lista contine doua noduri
suplimentare numite santinele: head si tail.
2017-2018
Variabile suplimentare
2017-2018
Adaugarea unui element
2017-2018
Sincronizare grosiera
• Clasa CoarseList satisface aceleasi proprietati de progres ca si
zavorul folosit. De exemplu, daca zavorul asigura lipsa
infometarii atunci si clasa are aceasta proprietate.
import java.util.concurrent.locks.*;
public CoarseList() {
head = new Node(Integer.MIN_VALUE);
head.next = tail = new Node(Integer.MAX_VALUE);
}
2017-2018
Sincronizare grosiera – adaugare element
public boolean add(T item) {
Node pred, curr;
int key = item.hashCode(); Punctul de liniarizare pentru orice
lock.lock(); metoda care achizitioneaza un zavor
try { este momentul achizitiei zavorului.
pred = head; curr = pred.next;
while (curr.key < key) {
pred = curr; curr = curr.next;
}
if (key == curr.key) {
return false;
} else {
Node node = new Node(item);
node.next = curr; pred.next = node;
return true;
}
} finally {
lock.unlock();
}
}
2017-2018
Sincronizare grosiera – eliminare element
public boolean remove(T item) {
Node pred, curr;
int key = item.hashCode();
lock.lock();
try {
pred = head; curr = pred.next;
while (curr.key < key) {
pred = curr; curr = curr.next;
}
if (key == curr.key) {
pred.next = curr.next;
return true;
} else {
return false;
}
} finally {
lock.unlock();
}
}
}
2017-2018
Sincronizare grosiera – apartenenta element
Tema !
2017-2018
Testare
• Test secvential:
– Firul curent executa succesiunea de adaugari ale elementelor 0, 1, …,
TEST_SIZE
– Firul curent executa succesiunea de teste de apartenenta ale
elementelor 0, 1, …, TEST_SIZE
– Firul curent executa succesiunea de eliminari ale elementelor 0, 1, …,
TEST_SIZE
• Test paralel:
– Se executa operatii concurente din firele 0, 1, ..., THREADS
– Fiecare fir executa PER_THREAD = TEST_SIZE / THREADS operatii
– Fiecare fir i = 0, 1, ..., THREADS se ocupa de elementele i
PER_THREAD, i PER_THREAD + 1, ..., i PER_THREAD +
PER_THREAD - 1 = (i+1) PER_THREAD - 1
2017-2018
Testare adaugare concurenta
• Fiecare fir i = 0, 1, ..., THREADS executa secvential
adaugarile elementelor i PER_THREAD, i
PER_THREAD + 1, ..., i PER_THREAD +
PER_THREAD - 1 = (i+1) PER_THREAD – 1
2017-2018
Testare folosind JUint
• Pentru testare se foloseste JUnit 4 (http://junit.org/) ce permite
definirea explicita a metodelor care specifica teste.
• Pentru fiecare modul se creaza o clasa de test care contine
metodele ce specifica testele.
• Specificarea unei metode ce desemneaza un test se face prin
adnotarea @Test (@org.junit.Test).
• Pentru implementarea testelor se poate folosi clasa Assert
(org.junit.Assert). Aceasta clasa pune la dispozitie o serie de
metode de forma assertX unde X reprezinta o conditie care este
verificata la executie. Spre exemplu X poate fi:
– True, False
– Equals, Same
– ...
2017-2018
Testarea clasei CoarseList
import org.junit.Test;
import org.junit.Assert;
public class CoarseListTest {
private final static int THREADS = 8;
private final static int TEST_SIZE = 128;
private final static int PER_THREAD = TEST_SIZE / THREADS;
CoarseList<Integer> instance;
Thread[] thread;
public CoarseListTest() {
instance = new CoarseList<Integer>();
thread = new Thread[THREADS];
}
2017-2018
Cuplarea zavoarelor
a b c
2017-2018
Cuplarea zavoarelor
a b c
2017-2018
Cuplarea zavoarelor
a b c
2017-2018
Cuplarea zavoarelor
a b c
2017-2018
Cuplarea zavoarelor
a b c
2017-2018
Adaugarea unui zavor clasei Node
private class Node {
T item;
int key;
Node next;
Lock lock;
Node(T item) {
this.item = item;
this.key = item.hashCode();
this.lock = new ReentrantLock();
}
Node(T key) {
this.item = null;
this.key = key;
this.lock = new ReentrantLock();
}
void lock() { lock.lock(); }
void unlock() { lock.unlock(); }
}
2017-2018
Adaugare element cu sincronizare fina I
public class FineList<T> {
Constructor similar pentru
private Node head,tail;
public FineList() {
fiecare tip de sincronizare.
head = new Node(Integer.MIN_VALUE);
head.next = tail = new Node(Integer.MAX_VALUE);
}
public boolean add(T item) {
int key = item.hashCode();
head.lock();
Node pred = head;
try {
curr = pred.next;
curr.lock();
try {
while (curr.key < key) {
pred.unlock();
pred = curr; curr = curr.next;
2017-2018
Adaugare element cu sincronizare fina II
curr.lock();
}
if (curr.key == key) {
return false;
}
Node newNode = new Node(item);
newNode.next = curr;
pred.next = newNode;
return true;
} finally { curr.unlock(); }
} finally { pred.unlock(); }
}
2017-2018
Eliminare element cu sincronizare fina
public boolean remove(T item) {
Node pred = head, curr = null;
int key = item.hashCode();
head.lock();
try {
pred = head; curr = pred.next;
curr.lock();
try {
while (curr.key < key) {
pred.unlock();
pred = curr; curr = curr.next;
curr.lock();
}
if (curr.key == key) {
pred.next = curr.next;
return true;
}
return false;
} finally { curr.unlock(); }
} finally { pred.unlock(); }
}
2017-2018
Verificare apartenenta cu sincronizare fina
TEMA !
2017-2018
Eliminare nod cu sincronizare fina
a b c d
remove(b)
2017-2018
Eliminare nod cu sincronizare fina
a b c d
remove(b)
2017-2018
Eliminare nod cu sincronizare fina
a b c d
remove(b)
2017-2018
Eliminare nod cu sincronizare fina
a b c d
remove(b)
2017-2018
Eliminare nod cu sincronizare fina
a b c d
remove(b)
2017-2018
Eliminare nod cu sincronizare fina
a c d
De ce avem nevoie de 2
remove(b)
zavoare?
2017-2018
Explicatie eliminare cu sincronizare fina – 1 zavor
• Sa presupunem ca un fir blocheaza doar elementul predecesor
elementului curent. Fie scenariul in care firul 𝐴 elimina 𝑎 in
timp ce firul 𝐵 elimina 𝑏. 𝐴 blocheaza accesul la ℎ𝑒𝑎𝑑 si 𝐵
blocheaza accesul la 𝑎. 𝐴 seteaza ℎ𝑒𝑎𝑑. 𝑛𝑒𝑥𝑡 la 𝑏, iar 𝐵
seteaza 𝑎. 𝑛𝑒𝑥𝑡 la 𝑐. In final se observa ca se elimina doar 𝑎, in
loc de a se elimina 𝑎 si 𝑏.
a b c d
remove(c)
remove(b)
2017-2018
Eliminare nod cu sincronizare fina si 1 zavor
a b c d
remove(c)
remove(b)
2017-2018
Eliminare nod cu sincronizare fina si 1 zavor
a b c d
remove(c)
remove(b)
2017-2018
Eliminare nod cu sincronizare fina si 1 zavor
a b c d
remove(c)
remove(b)
2017-2018
Eliminare nod cu sincronizare fina si 1 zavor
a b c d
remove(c)
remove(b)
2017-2018
Eliminare nod cu sincronizare fina si 1 zavor
a b c d
remove(c)
remove(b)
2017-2018
Eliminare nod cu sincronizare fina si 1 zavor
a b c d
remove(c)
remove(b)
2017-2018
Eliminare nod cu sincronizare fina si 1 zavor
a b c d
remove(c)
remove(b)
2017-2018
Problema ?
a c d
remove(c)
remove(b)
2017-2018
Problema – c nu este eliminat !
a c d
remove(c)
remove(b)
2017-2018
Explicatie problema
• Pentru a elimina pe 𝑐, nodul 𝑏 trebuie redirectat catre
succesorul lui 𝑐.
a b c
2017-2018
Explicatie eliminare cu sincronizare fina – 2 zavoare
• Regula de transmitere din “mana-in-mana” a zavoarelor ne
asigura ca daca doua apeluri concurente ale metodei remove()
incearca sa elimine noduri adiacente (cum sunt a si b) va
aparea un conflict pe zavorul unui nod comun (A acceseaza
head si a, in timp ce B acceseaza a si b), fortand unul dintre
apeluri sa astepte dupa celalalt.
a b c d
remove(c)
remove(b)
2017-2018
Eliminare nod cu sincronizare fina si 2 zavoare
a b c d
remove(c)
remove(b)
2017-2018
Eliminare nod cu sincronizare fina si 2 zavoare
a b c d
remove(c)
remove(b)
2017-2018
Eliminare nod cu sincronizare fina si 2 zavoare
a b c d
remove(c)
remove(b)
2017-2018
Eliminare nod cu sincronizare fina si 2 zavoare
a b c d
remove(c)
remove(b)
2017-2018
Eliminare nod cu sincronizare fina si 2 zavoare
a b c d
remove(c)
remove(b)
2017-2018
Eliminare nod cu sincronizare fina si 2 zavoare
a b c d
remove(c)
remove(b)
2017-2018
Eliminare nod cu sincronizare fina si 2 zavoare
a b c d
remove(c)
remove(b)
2017-2018
Eliminare nod cu sincronizare fina si 2 zavoare
a b c d
Must remove(c)
acquire
Lock for
b
2017-2018
Eliminare nod cu sincronizare fina si 2 zavoare
a b c d
remove(c)
Cannot
acquire
lock for b
2017-2018
Eliminare nod cu sincronizare fina si 2 zavoare
a b c d
remove(c)
Wait!
2017-2018
Eliminare nod cu sincronizare fina si 2 zavoare
a b d
Proceed
to
remove(b)
2017-2018
Eliminare nod cu sincronizare fina si 2 zavoare
a b d
remove(b)
2017-2018
Eliminare nod cu sincronizare fina si 2 zavoare
a b d
remove(b)
2017-2018
Eliminare nod cu sincronizare fina si 2 zavoare
a d
remove(b)
2017-2018
Eliminare nod cu sincronizare fina si 2 zavoare
a d
2017-2018
Evitarea interblocarii in sincronizarea fina
• Pentru evitarea interblocarii firele trebuie sa achizitioneze zavoarele in
aceeasi ordine, de la ℎ𝑒𝑎𝑑 inspre 𝑡𝑎𝑖𝑙. De exemplu, daca firul 𝐴 ce
adauga 𝑎 blocheaza intai 𝑏 si apoi ℎ𝑒𝑎𝑑, iar firul 𝐵 ce doreste sa stearga
pe 𝑏 blocheaza intai ℎ𝑒𝑎𝑑 si apoi 𝑏, fiecare fir va detine un zavor pe care
celalalt fir doreste sa-l achizitioneze, conducand la interblocaj.
• Avantaj:
– Este mai buna decat sincronizarea grosiera deoarece firele
pot accesa lista in maniera concurenta.
• Dezavantaj:
– Se creaza siruri lungi de achizitii-eliberari de zavoare ce
sunt ineficiente.
2017-2018
Sincronizare optimista
• Se foloseste asupra obiectelor reprezentate ca multimi de
noduri (componente) inlantuite prin referinte – liste si arbori,
in situatia in care operatiile presupun cautarea unui anumit nod
(componenta).
• Pentru reducerea costului suplimentar prezent in sincronizarea
fina, cautarea se va realiza fara achizitia de zavoare. Odata
gasita, accesul la componenta respectiva este blocat cu un
zavor, se re-verifica faptul ca acea componenta nu a fost
modificata intre timp, si apoi se executa operatia. In cazul in
care re-verificarea esueaza se reia procedeul de cautare.
• Metoda este eficienta daca probabilitatea de reusita a operatiei
de cautare este mai mare decat probabilitatea de esec.
2017-2018
Adaugarea unui element cu sincronizare optimista
public boolean add(T item) { Verifica daca pred este accesibil
int key = item.hashCode(); din head si daca pred.next este
while (true) { egal cu curr.
Node pred = head, curr = pred.next;
while (curr.key < key) { pred = curr; curr = curr.next; }
pred.lock(); curr.lock();
try {
if (validate(pred, curr)) {
if (curr.key == key) { return false;
} else {
Node node = new Node(item);
node.next = curr; pred.next = node;
return true;
}
}
} finally {
pred.unlock(); curr.unlock();
}
}
}
2017-2018
Eliminarea unui element cu sincronizare optimista
public boolean remove(T item) {
int key = item.hashCode();
while (true) {
Node pred = head, curr = pred.next;
while (curr.key < key) { pred = curr; curr = curr.next; }
pred.lock(); curr.lock();
try {
if (validate(pred, curr)) {
if (curr.key == key) {
pred.next = curr.next;
return true;
} else { return false; }
}
} finally {
pred.unlock(); curr.unlock();
}
}
}
2017-2018
Metoda validate()
public boolean validate(Node pred, Node curr) {
Node node = head;
while (node.key <= pred.key) {
if (node == pred) return pred.next == curr;
node = node.next;
}
return false;
}
• Firul 𝐴 elimina 𝑎. In timpul traversarii este posibil ca toate nodurile de la 𝑐𝑢𝑟𝑟𝐴 incolo
(inclusiv 𝑎) sa fie eliminate. Insa firul 𝐴 va ajunge la 𝑎 “eliminandu-l” incorect.
TEMA !
2017-2018
Concluzii asupra sincronizarii optimiste
• Avantaj:
– Poate duce la cresterea performantei (prin cresterea concurentei),
folosind mai putin zavoarele
• Posibilitatea infometarii:
– Clasa OpimisticList nu ofera garantia lipsei infometarii. Este posibil ca
un fir care acceseaza un obiect al acestei clase sa fie intarziat la infinit,
in timp ce alte fir executa in mod repetat operatii de adaugare /
eliminare de noduri.
• Ineficienta:
– Lista este intotdeauna parcursa de cel putin doua ori
2017-2018
Sincronizare lenesa
• Foloseste o tehnica de amanare a operatiilor considerate mai
dificile. De exemplu, operatia de eliminare a unei componente
dintr-o structura de date se poate diviza in doua:
– Eliminarea logica prin marcarea cu un camp 𝑏𝑜𝑜𝑙𝑒𝑎𝑛 special
– Eliminarea fizica prin deconectarea ei de la structura de date
• Se adauga un camp marked initializat cu false la clasa Node.
Cand marked devine true inseamna ca elementul a fost eliminat
“logic” din multime.
• Nu mai este nevoie sa se blocheze accesul la noduri cu zavor.
• Nu mai este necesara validarea prin reparcurgerea listei.
• Elementele multimii sunt date de nodurile nemarcate ce pot fi
atinse din head. Daca un element nu este gasit sau are marked
pe true atunci el nu apartine multimii.
2017-2018
Implementarea operatiilor in sincronizarea lenesa
• Operatia contains() necesita o simpla traversare. Aceasta
operatie este wait-free.
• Operatia add() traverseaza lista, blocheaza accesul la
predecesorul nodului tinta (inaintea caruia are loc inserarea) si
insereaza nodul. Daca traversarea nu gaseste nodul sau il
gaseste cu marked=true atunci ea intoarce false.
• Operatia remove() este “lenesa” si lucreaza in doi pasi. In
primul pas ea marcheaza nodul tinta pentru stergere “logica”.
In pasul al doilea ea realizeaza redirectionarea campului 𝑛𝑒𝑥𝑡
al nodului predecesor, realizand stergerea “fizica” a nodului
tinta.
2017-2018
Adaugarea unui element cu sincronizare lenesa
public boolean add(T item) {
int key = item.hashCode();
while (true) {
Node pred = head, curr = head.next;
while (curr.key < key) { pred = curr; curr = curr.next; }
pred.lock();
try {
curr.lock();
try {
if (validate(pred, curr)) {
if (curr.key == key) { return false;
} else {
Node node = new Node(item);
node.next = curr; pred.next = node;
return true;
}
}
} finally { curr.unlock(); }
} finally { pred.unlock(); }
}
}
2017-2018
Eliminarea unui element cu sincronizare lenesa
public boolean remove(T item) {
int key = item.hashCode();
while (true) {
Node pred = head, curr = head.next;
while (curr.key < key) {
pred = curr; curr = curr.next;
}
pred.lock();
try {
curr.lock();
try {
if (validate(pred, curr)) {
if (curr.key != key) { return false;
} else {
curr.marked = true; pred.next = curr.next;
return true;
}
}
} finally { curr.unlock(); }
} finally { pred.unlock(); }
}
2017-2018
}
Verificare apartenenta cu sincronizare lenesa
public boolean contains(T item) {
int key = item.hashCode();
Node curr = head;
while (curr.key < key)
curr = curr.next;
return curr.key == key && !curr.marked;
}
2017-2018
Metoda validate() in sincronizarea lenesa
2017-2018
Concluzii asupra sincronizarii lenese
• Avantaj:
– Se separa pasii de stergere logica prin realizarea marcajului de pasii
care realizeaza stergerea fizica prin eliminarea unui nod. Pe cazul
general operatiile care realizeaza stergerea fizica pot fi grupate si
realizate la un moment convenabil ulterior, astfel incat sa se reduca
efectul operatiilor ce afecteaza structura listei
• Dezavantaj
– Apelurile add() si remove() folosesc zavoare, fapt putand conduce la
intarzierea nelimitata a operatiilor respective.
2017-2018
Sincronizare neblocanta
2017-2018
Sincronizare neblocanta – solutie incorecta
• Fie 𝑎 si 𝑏 doua noduri adiacente ale listei, 𝑏 succesorul lui 𝑎.
Metoda naiva nu functioneaza in cazurile in care se executa
concurent:
– A: remove(a) si B: add(b)
– A: remove(a) si B:remove(b) (Tema: ?)
• Un fir A sterge logic nodul curr marcand campul next. Alte fire
ce executa operatii add() sau remove() traverseaza lista,
concomitent stergand fizic nodurile marcate cu ajutorul
metodei compareAndSet().
2017-2018
Modificarea clasei Node
public Node(int k) {
key = k;
item = null;
next = new AtomicMarkableReference<Node>(null, false);
}
public Node(T i) {
item = i;
key = i.hashCode();
next = new AtomicMarkableReference<Node>(null, false);
}
}
2017-2018
Clasa LockFreeList<T>
public class LockFreeList<T> {
private Node head;
public LockFreeList() {
head = new Node(Integer.MIN_VALUE);
head.next = new AtomicMarkableReference<Node>(
new Node(Integer.MAX_VALUE), false);
}
class Window {
public Node pred;
public Node curr;
Window(Node pred, Node curr) {
this.pred = pred; this.curr = curr;
}
}
// ...
}
2017-2018
Clasa interna Window
• Se creaza o clasa ajutatoare Window. Fiind dat un element a,
metoda find() a listei parcurge lista si determina un obiect
Window ce incapsuleaza doua noduri pred si curr a.i.:
– pred este nodul cu cea mai mare cheie mai mica decat a
– curr este nodul cu cea mai mica cheie mai mare sau egala cu a.
• Parcurgerea listei realizeaza stergerea fizica a nodurilor
marcate. Daca in momentul incercarii operatiei de stergere se
constata ca intre timp lista a fost modificata, procesul de
parcurgere al listei este reluat.
• Varianta in care stergerea fizica a elementului curr s-ar realiza
imediat dupa marcare nu este corecta deoarece alte fire pot fie
sterge concurent nodul pred, fie adauga concurent noi noduri
intre pred si curr.
2017-2018
Metoda find() I
2017-2018
Metoda find() II
public Window find(Node head, int key) {
Node pred = null, curr = null, succ = null;
boolean[] marked = {false};
boolean snip;
retry: while (true) {
pred = head;
curr = pred.next.getReference();
while (true) {
succ = curr.next.get(marked);
while (marked[0]) {
snip = pred.next.compareAndSet(curr,succ,false,false);
if (!snip) continue retry;
curr = succ;
succ = curr.next.get(marked);
}
if (curr.key >= key)
return new Window(pred, curr);
pred = curr;
curr = succ;
}
}
}
2017-2018
Adaugarea unui element cu sincronizare neblocanta
public boolean add(T item) {
int key = item.hashCode();
while (true) {
Window window = find(head, key);
Node pred = window.pred, curr = window.curr;
if (curr.key == key) {
return false;
} else {
Node node = new Node(item);
node.next =
new AtomicMarkableReference<Node>(curr, false);
if (pred.next.compareAndSet(curr,node,false, false)){
return true;
}
}
}
}
2017-2018
Concluzii asupra sincronizarii neblocante
• Avantaj:
– Garanteaza progresul chiar si in prezenta unor intarzieri arbirare.
• Dezavantaj
– Necesitatea folosirii unei operatii de modificare atomica a unei
referinte impreuna cu marcajul logic; aceasta operatie aduce costuri
suplimentare in implementare.
– Poate fi necesar sa se reparcurga lista in cazul unor operatii
concurente.
2017-2018