This is an old revision of the document!
Sa presupunem ca avem 2 thread-uri: thread-ul 0 care calculeaza o valoare si o plaseaza in variabila `a` si thread-ul 1 care atunci cand variabila `a` se actualizeaza, o afiseaza in interfata grafica a unui program. O posibila implementare pentru functia thread-ului 0, in pseudocod, ar fi:
function_thread_0: noua_valoare = calculeaza_valoare_a() mutex_a.lock() a = noua_valoare mutex_a.unlock()
O posibila implementare pentru functia thread-ului 1, in pseudocod, ar fi:
function_thread_1: while(a nu s-a actualizat): mutex_a.lock() if (a s-a actualizat): afiseaza(a) a = -1 // Resetam valoarea lui a. mutex_a.unlock()
Observam o problema cu aceasta abordare: thread-ul 1 va cicla in bucla while de mai multe ori, chiar daca thread-ul 0 nu-l actualizeaza pe `a`, ocupand inutil timp pe procesor. Aceasta abordare poarta numele de busy waiting. Desi busy waiting nu este intotdeauna o idee rea, in cazul de fata, prespunuem ca pentru a calcula noua valoarea a lui a, thread-ului 0 ii ia un timp indelungat. Astfel, exista o solutie mai buna oferita de primitevele Java wait(), notify() și notifyAll().
wait() - forțează thread-ul curent să intre în așteptare până când alt thread apelează notify() sau notifyAll() pe același obiect. Pentru ca aceasta să se întâmple, thread-ul curent trebuie să dețină monitorul obiectului respectiv. Deținerea monitorului se poate îmtâmpla în următoarele situații:
String myMonitorObject = ””;
), deoarece JVM optimizează intern accesul la astfel de variabile, având doar o singură instanță în cadrul programului.
notify() - alege aleatoriu un thread care așteaptă (a apelat wait()) monitorul obiectului respectiv și trece-l din starea de waiting în starea de running
notifyAll() - trece toate thread-urile care așteaptă (au apelat wait()) monitorul obiectului respectiv și trece-le din starea de waiting în starea de running
Un semafor poate fi văzut ca un lock ce permite mai multor thread-uri să coexiste într-o anumită regiunie critică la un moment dat. Semaforul folosește un contor care determină câte thread-uri mai pot intra. Odată ajuns la semafor, un thread este lăsat să intre doar dacă numărul de thread-uri aflate în zona critică este mai mic decât numărul maxim de thread-uri setat la crearea semaforului.
Pe un semafor se pot realiza următoarele operații:
class Something { static int resource = 0; } public class MySemaphore extends Thread { private int id; private Semaphore sem; public MySemaphore(int id, Semaphore sem) { this.id = id; this.sem = sem; } public static void main(String args[]) { Semaphore sem = new Semaphore(1); MySemaphore mt1 = new MySemaphore(0, sem); MySemaphore mt2 = new MySemaphore(1, sem); mt1.start(); mt2.start(); try { mt1.join(); mt2.join(); } catch (InterruptedException ex) { Logger.getLogger(MySemaphore.class.getName()).log(Level.SEVERE, null, ex); } System.out.println("count: " + Something.resource); } @Override public void run() { switch (this.id) { case 0: System.out.println("Starting thread " + id); try { System.out.println("Thread " + id + " is waiting for a permit."); sem.acquire(); System.out.println("Thread " + id + " gets a permit."); for (int i = 0; i < 5; i++) { Something.resource++; System.out.println("Thread " + id + ": " + Something.resource); Thread.sleep(10); } } catch (InterruptedException exc) { System.out.println(exc); } System.out.println("Thread " + id + " releases the permit."); sem.release(); break; case 1: System.out.println("Starting thread " + id); try { System.out.println("Thread " + id + " is waiting for a permit."); sem.acquire(); System.out.println("Thread " + id + " gets a permit."); for (int i = 0; i < 5; i++) { Something.resource--; System.out.println("Thread " + id + ": " + Something.resource); Thread.sleep(10); } } catch (InterruptedException exc) { System.out.println(exc); } // Release the permit. System.out.println("Thread " + id + " releases the permit."); sem.release(); break; } } public int getThreadId() { return this.id; } }
În codul de mai sus se creează un semafor ce acceptă un singur thread în zona critică (observați forma constructorului Semaphore). Practic, acest semafor este similar cu un lock. Semaforul se creează in main si se trimite ca parametru în constructorul thread-urilor. În cadrul metodei run, thread-urile încearcă să apeleze acquire, însă doar unul dintre ele va reuși să intre în zona de după semafor. Ulterior, după ce se realizează operațiile din zona critică, thread-ul respectiv anunță faptul că a ieșit prin intermediul unui apel de release. Observați faptul că, în funcție de thread-ul care reușește să intre în zona respectivă, contorul poate scădea până la -5, urmând să revină la 0 apoi, sau să fie crescut până la 5, urmând să ajungă la 0 apoi.
Problema se referă la două thread-uri: producător și consumator. Producătorul inserează date într-un buffer, iar consumatorul extrage date din acel buffer. Buffer-ul are o dimensiune prestabilită, astfel că:
O implementare corectă a problemei presupune asigurarea faptului că nu vor exista situații de deadlock, adică situații în care cele două thread-uri așteaptă unul după celălalt, neexitând posibilitatea de a se debloca.
Această problemă se poate rezolva în mai multe moduri (rezolvările sunt mai sus, în cadrul textului laboratorului):
Pseudocod - variante cu semafoare:
T buffer = new T[k]; semaphore gol(k); semaphore plin(0); mutex mutexP, mutexC; producer(int id) { T v; while (true) { v = produce(); gol.acquire(); mutexP.lock(); buf.add(v); mutexP.unlock(); plin.release(); } } consumer(int id) { T v; while (true) { plin.acquire(); mutexC.lock(); v = buf.poll(); mutexC.unlock(); gol.release(); consume(v); } }
Problema se referă la mai mulți filozofi (thread-uri) așezați la o masă circulară. Pe masă se află 5 farfurii și 5 tacâmuri, astfel încât fiecare filozof are un tacâm în stânga și unul în dreapta lui. În timp ce stau la masă, filozofii pot face două acțiuni: mănâncă sau se gândesc. Pentru a mânca, un filozof are nevoie de două tacâmuri (pe care le poate folosi doar dacă nu sunt luate de către vecinii săi).
Rezolvarea trebuie să aibă în vedere dezvoltarea unui algoritm prin care să nu se ajungă la un deadlock (situația în care fiecare filozof ține câte un tacâm în mână și așteaptă ca vecinul să elibereze celălalt tacâm de care are nevoie).
Ca soluție, avem în felul următor: vom avea N lock-uri (având în vedere că avem N thread-uri), fiecare filosof va folosi câte două lock-uri. Pentru a evita deadlock-ul, totul va funcționa în felul următor:
Pseudocod:
Lock[] locks = new Lock[N]; philosopher(int id) { while (true) { if (id != N - 1) { locks[id].lock(); locks[id + 1].lock(); // eat locks[id].release(); locks[id + 1].release(); // think } else { locks[0].lock(); locks[N - 1].lock(); // eat locks[0].release(); locks[N - 1].release(); // think } } }
Avem o zonă de memorie asupra căreia au loc mai multe acțiuni de citire și de scriere. Această zonă de memorie este partajată de mai multe thread-uri, care sunt de două tipuri: cititori (care execută acțiuni de citire din zona de memorie) și scriitori (care execută acțiuni de scriere în zona de memorie).
În această privință avem niște constrângeri:
Pentru această problemă avem două soluții:
Folosind această soluție, un cititor nu va aștepta ca ceilalți cititori să termine de citit zona de memorie, chiar dacă avem un scriitor care așteaptă. Un scriitor poate să aștepte foarte mult, în caz că sunt foarte mulți scriitor, fapt ce poate duce la un fenomen numit writer's starvation.
De asemenea, nu poate să intre un scriitor cât timp există deja un scriitor care scrie în zona de memorie partajată.
Pseudocod:
int readers = 0; mutex mutexNumberOfReaders; // sau semaphore mutexNumberOfReaders(1); semaphore readWrite(1); // sau mutex readWrite reader (int id) { while (true) mutexNumberOfReaders.lock(); readers = readers + 1; if (readers == 1) { readWrite.acquire(); // dacă e primul cititor }; mutexNumberOfReaders.unlock(); // citește din resursa comună; mutexNumberOfReaders.lock(); readers = readers - 1; if (readers == 0) { readWrite.release(); // dacă e ultimul cititor } mutexNumberOfReaders.unlock(); } } writer(int id) { while (true) { readWrite.acquire(); // scrie în resursa comună; readWrite.release(); } }
Folosind această soluție, niciun cititor nu va intra în zona de memorie partajată cât timp există un scriitor care scrie în zona de memorie. De asemenea, nu poate să intre alt scriitor cât timp există un scriitor care se află în zona de memorie partajată.
Pseudocod:
int readers = 0; // cititori care citesc din zona de memorie int writers = 0; // scriitori care scriu în zona de memorie (va fi doar unul) int waiting_readers = 0; // cititori care așteaptă să intre în zona de memorie int waiting_writers = 0; // scriitori care așteaptă să intre în zona de memorie // semafor folosit pentru a pune scriitori în așteptare, dacă avem un scriitor sau unul sau mai mulți cititori în zona de memorie (zona critică) semaphore sem_writer(0); // semafor folosit pentru a pune cititori în așteptare dacă avem un scriitor care scrie în zona de memorie sau dacă avem scriitori în așteptare (deoarece ei au prioritate față de cititori) semaphore sem_reader(0); semaphore enter(1); // semafor folosit pe post de mutex pentru intrarea în zona de memorie (zona critică) reader(int id) { while(true) { enter.acquire(); if (writers > 0 || waiting_writers > 0) { waiting_readers++; enter.release(); sem_reader.acquire(); } readers++; if (waiting_readers > 0) { waiting_readers--; sem_reader.release(); } else if (waiting_readers == 0) { enter.release(); } // citește din zona partajată enter.acquire(); readers--; if (readers == 0 && waiting_writers > 0) { waiting_writers--; sem_writer.acquire(); } else if (readers > 0 || waiting_writers == 0) { enter.release(); } } } reader(int id) { while(true) { enter.acquire(); if (readers > 0 || writers > 0) { waiting_writers++; enter.release(); sem_writer.acquire(); } writers++; enter.release(); // scrie în zona partajată enter.acquire(); writers--; if (waiting_readers > 0 && waiting_writers == 0) { waiting_readers--; sem_reader.release(); } else if (waiting_writers > 0) { waiting_writers--; sem_writer.release(); } else if (waiting_readers == 0 && waiting_writers == 0) { enter.release(); } } }