ALGORITMI PARALELI ȘI DISTRIBUIȚI ÎNDRUMAR DE LABORATOR ...€¦ · Regiunea paralela = bloc de...

47
ALGORITMI PARALELI ȘI DISTRIBUIȚI ÎNDRUMAR DE LABORATOR / SEMINAR

Transcript of ALGORITMI PARALELI ȘI DISTRIBUIȚI ÎNDRUMAR DE LABORATOR ...€¦ · Regiunea paralela = bloc de...

Page 1: ALGORITMI PARALELI ȘI DISTRIBUIȚI ÎNDRUMAR DE LABORATOR ...€¦ · Regiunea paralela = bloc de cod care se va executa de mai multe thread-uri. La o directivă parallel un thread

ALGORITMI PARALELI ȘI DISTRIBUIȚI

ÎNDRUMAR DE LABORATOR / SEMINAR

Page 2: ALGORITMI PARALELI ȘI DISTRIBUIȚI ÎNDRUMAR DE LABORATOR ...€¦ · Regiunea paralela = bloc de cod care se va executa de mai multe thread-uri. La o directivă parallel un thread

LISTA LUCRĂRILOR

1. Introducere în clasa Thread2. Introducere în bibliotecile OpenMP3. Implementare fire de execuție4. Implementare semafoare5. Implementarea şi testare algoritm de sortare paralelă6. Implementarea unor algoritmi din metodele numerice.7. Testarea algoritmilor paraleli

Page 3: ALGORITMI PARALELI ȘI DISTRIBUIȚI ÎNDRUMAR DE LABORATOR ...€¦ · Regiunea paralela = bloc de cod care se va executa de mai multe thread-uri. La o directivă parallel un thread

LABORATORUL NR.1Introducere în clasa Thread

Scop: 1. Înțelegrea noțiunilor privind implemementarea accesului concurențial prin clasa Thread

Consideraţii teoretice:Sistemele multiprocesor: calculele sunt repartizate mai multor procesoare fizice. În aceste sistememultiprocesor, comunicarea între procesoare are loc fie prin zone de memorie comune (partajate), fie princanale.

Algoritmi de calcul paralel (care determină soluţia unei probleme prin descompunerea ei însubprobleme independente, rezolvabile în paralel pe procesoare distincte); de exemplu este evident că suma adoi vectori, calculată prin: for i:=1 to n do c[i]:=a[i]+b[i]se efectuează mai rapid dacă avem la dispoziţie n procesoare, fiecare capabil să efectueze o adunare. Programare secvențială VS Programare concurentăProgramarea concurentă rezervă o serie de surprize informaticianului obişnuit cu programarea secvenţială.Prezentăm în continuare câteva dintre ele.

1) În programarea secvenţială, următoarea secvenţă de instrucţiuni: i:=1; i:=2este echivalentă cu a doua instrucţiune. În programarea concurentă secvenţa de mai sus nu are nimic redundant. Într-adevăr, în intervalul de timpdintre executarea celor două instrucţiuni (în cadrul aceluiaşi proces) este posibil ca celelalte procese să executediferite instrucţiuni care să folosească efectiv faptul că un anumit interval de timp (asupra lungimii căruia nuputem face nici o ipoteză) valoarea lui i este egală cu 1.

2) Efectul instrucţiunii: if i=1 then j:=j+iîn cazul în care valoarea lui i era 1 la începutul executării acestei instrucţiuni condiţionale nu constă neapărat înmărirea cu o unitate a valorii lui j, deoarece între momentul efectuării comparaţiei şi momentul efectuăriiatribuirii valoarea lui i poate fi eventual modificată de un alt proces. Analog, dacă într-un proces aparesecvenţa de instrucţiuni: i:=1; if i=1 then instratunci pe de o parte nu este neapărat adevărat că este îndeplinită condiţia i=1 din instruţiunea if, iar pe de altăparte nu este neapărat adevărat că în momentul executării instrucţiunii instr valoarea lui i este egală cu 1; înschimb afirmaţia valoarea lui i a fost egală cu 1 la un moment de timp anterior" este adevărată şi poate fi utilă(de exemplu în a demonstra că procesul are o anumită evoluţie).Noţiunea de secţiune critică

Problema rezervării biletelor. Reamintim că problema constă în simularea activităţii mai multorterminale conectate la un calculator central, terminale de la care se pot face rezervări de bilete pentru unanumit spectacol; bineînţeles că trebuie evitată vânzarea a două bilete pentru acelaşi loc.

Considerăm următoarea modalitate prin care un proces face rezervarea, în ipoteza că mai există locuri

Page 4: ALGORITMI PARALELI ȘI DISTRIBUIȚI ÎNDRUMAR DE LABORATOR ...€¦ · Regiunea paralela = bloc de cod care se va executa de mai multe thread-uri. La o directivă parallel un thread

libere şi că fiecare persoană ce accesează un terminal are anumite preferinţe pentru locurile din sală, dar estehotărâtă să cumpere un bilet:repeat rez:=false; { soseşte un nou client }repeat { până când clientul cere un loc liber } expune planul sălii read(loc_client); { citeşte locul cerut de client } if sala[loc_client]=1 { locul este liber } then begin sala[loc_client]:=0; rez:=true; eliberează bilet end else write('Alta optiune: ') until rezforever

Este uşor de observat că această modalitate este incorectă. Vom numi secţiune critică o secvenţă de instrucţiuni a căror executare trebuie să se supună

următoarei reguli: în momentul în care un proces P începe executarea primei instrucţiuni din secvenţă, toatecelelalte procese îşi întrerup temporar activitatea până la terminarea executării ultimei instrucţiuni dinsecvenţă de către procesul P. În aceste condiţii spunem că are loc o excludere reciprocă (cel mult un proces sepoate afla într-o secţiune critică).

Să observăm că dacă în codul de mai sus ataşat instrucţiunii if primele patru acţiuni (linii de cod) arforma o secţiune critică, atunci modalitatea de rezervare de mai sus ar fi corectă.Crearea firelor de executare

Clasa Thread

Un prim mod de a crea şi lansa în executare fire este oferit de clasa Thread din pachetul java.lang:public class Thread extends Object implements Runnable

Crearea firului se face astfel:Thread T = new Thread();

După creare, un fir este lansat în executare prin invocarea metodei run. Aceasta este o metodă aclasei Thread şi nu prevede vreo acţiune; de aceea ea trebuie redefinită conform dorinţelor programatorului.

Când metoda run se termină, se termină şi firul care a invocat-o.Lansarea în executare a unui fir face ca acesta să fie executat concurent cu firul care l-a lansat. De

aceea invocăm metoda run prin start.

Page 5: ALGORITMI PARALELI ȘI DISTRIBUIȚI ÎNDRUMAR DE LABORATOR ...€¦ · Regiunea paralela = bloc de cod care se va executa de mai multe thread-uri. La o directivă parallel un thread

Exemplul 1.

class Tip extends Thread { int k; boolean b=true;

public void run() { while(b) IO.write(" " + k++); IO.writeln("Thread"); }

public void terminare() { b=false; }}

class P1 { public static void main(String[] w) Tip Ob = new Tip(); Ob.start(); while ( IO.readch() != '\n' ); Ob.terminare(); IO.writeln("main"); }}

Dacă în loc de Ob.start() am fi folosit Ob.run(), programul ar rula la infinit (programul principalaşteaptă ca obiectul Ob să-şi termine acţiunea) !!! Interfaţa Runnable

O a doua modalitate de a crea şi lansa în executare fire este de a folosi interfaţa Runnable, ce apareîn pachetul java.lang şi anunţă numai metoda run.

class Execute implements Runnable { . . . public void run() { . . . } . . .}

class C { . . . Execute Ob = new Execute(...); Thread T = new Thread(Ob); // firul este creat T.start(); // firul este pornit . . .}

Page 6: ALGORITMI PARALELI ȘI DISTRIBUIȚI ÎNDRUMAR DE LABORATOR ...€¦ · Regiunea paralela = bloc de cod care se va executa de mai multe thread-uri. La o directivă parallel un thread

Exemplul 2.

class Execute implements Runnable { int k; boolean b=true; public void run() { while (b) IO.write(" " + k++); IO.writeln("Thread"); } public void terminate() { b = false; }}

class RunThread { public static void main(String[] arg) { Execute Ob = new Execute(); Thread T = new Thread(Ob); T.start(); while ( IO.readch() != '\n'); Ob.terminate(); IO.writeln("main"); }}

Desfăşurarea lucrării:1. Se copiază exemplele prezentate şi se vor analiza rezultatele prin repetarea execuției2. Se vor extinde instanțierile existente şi se va face o analiză a rezultatului

Page 7: ALGORITMI PARALELI ȘI DISTRIBUIȚI ÎNDRUMAR DE LABORATOR ...€¦ · Regiunea paralela = bloc de cod care se va executa de mai multe thread-uri. La o directivă parallel un thread

LABORATORUL NR.2Introducere în bibliotecile OpenMP

Scop: 1.

Consideraţii teoretice:

Desfăşurarea lucrării:Pentru compilarea unui program ce utilizează OpenMP cu gcc sai g++ vom utiza opțiunea -fopenmp atât pentru compilare cât şi pentru linkeditare.În momentul instalării MinGW /TDM se va selecta opțiunea OpenMP pentru aducerea acestor biblioteci.Pentru a executa in paralel 2 portiuni de cod (se vor aduna 2 matrici) folosind OpenMP, se folosesc 2 blocuri omp section, aceste 2 blocuri fiind cuprinse intr-un bloc omp parallel sections.Un program OpenMP incepe cu un proces singular (thread master) -> secvential -> constructie de regiune paralela (FORK) -> mai multe thread-uri in paralel -> JOIN -> thread-ul master etc.Numarul de thread-uri : se poate modifica dinamic. Structura unui program OpenMP :

#include <opm.h>void main ( ){

int var1, var2, var3;..... cod secvential .....

//incepe sectiunea paralela => FORK#pragma omp parallel private (var1, var2) shared (var3){

..... sectiune paralela executata de toate thread-urile .....//toate thread-urile => JOIN => thread master}

..... reia cod secvential .....}

Formatul unei directive

#pragma omp nume_directiva [clauza, clauza, ...] newline

unde clauzele pot fi plasate in orice ordine si se pot chiar repeta. Liniile directive lungi se pot continua perandul urmator cu ’\’.

Directiva pentru regiune paralela

Page 8: ALGORITMI PARALELI ȘI DISTRIBUIȚI ÎNDRUMAR DE LABORATOR ...€¦ · Regiunea paralela = bloc de cod care se va executa de mai multe thread-uri. La o directivă parallel un thread

Regiunea paralela = bloc de cod care se va executa de mai multe thread-uri.

La o directivă parallel un thread crează un set de thread-uri si devine masterul setului (cu numărul de thread 0 în cadrul setului).Numarul de thread-uri : omp_set_num_threads() sau cu variabila de mediu OMP_NUM_THREADS.Clauza if evalueaza expresia scalara: daca expresia ≠ 0 (adevarat) se creaza setul de thread-uri, iar daca expresia = 0 (fals) regiunea este executata numai de thread-ul master.La sfarsitul sectiunii paralele numai thread-ul master isi continua executia.Exemplu: toate thread-urile executa codul corespunzator sectiunii paralele.

> Directiva pentru partajarea lucrului Aceasta directiva imparte regiunea de cod intre thread-urile membre, fara insa sa lanseze noi thread-uri.Constructiile de partajare a lucrului sunt:

-for;-sections;-single.

O constructie de partajare a lucrului trebuie inclusa dinamic intr-o regiune paralela pentru a fi executata inparalel.> Directiva for

Page 9: ALGORITMI PARALELI ȘI DISTRIBUIȚI ÎNDRUMAR DE LABORATOR ...€¦ · Regiunea paralela = bloc de cod care se va executa de mai multe thread-uri. La o directivă parallel un thread

Partajeaza iteratiile unei bucle for intre thread-urile setului (reprezinta un tip de paralelism de date):Forma generala a directivei for este:

Clauza schedule, in functie de tip, descrie cum se impart iteratiile buclei for intre thread-urile din set:-static: iteratiile se impart in sectiuni de dimensiune chunk si asignate static thread-urilor (daca nu

se specifica chunk, iteratiile se impart in mod egal).-dynamic: iteratiile se impart in sectiuni de dimensiune chunk si se asigneaza dinamic thread-urilor.

Cand un thread termina bucata sa, acesta este asignat dinamic la o alta bucata din totalul de iteratii (valoarea implicita chunk=1).

-guided: dimensiunea bucatii este redusa exponential cu fiecare bucata repartizata din totalul iteratiilor. Dimensiunea bucatii reprezinta numarul minim de iteratii de repartizat de fiecare data (implicit chunk=1).

-runtime: decizia de repartizare este amanata pana in timpul executiei, fiind determinata de variabila de mediu OMP_SCHEDULE (nu se specifica dimensiune chunk).

Clauza ordered trebuie sa fie prezenta cand sunt incluse in directiva for si directive ordered.

Page 10: ALGORITMI PARALELI ȘI DISTRIBUIȚI ÎNDRUMAR DE LABORATOR ...€¦ · Regiunea paralela = bloc de cod care se va executa de mai multe thread-uri. La o directivă parallel un thread

Clauza nowait indica faptul ca thread-ul nu se sincronizeaza la sfarsitul buclei paralele. Exemplu: program pentru adunarea a doi vectori.

Directiva sectionsImparte lucrul in sectiuni discrete separate, fiecare sectiune fiind executata de un thread, pentru implementarea unui paralelism functional. Forma generala a directivei sections este:

Fiecare sectiune „section” se executa o singura data de catre un singur thread, sectiuni diferite vor fi executate de thread-uri diferite. Exista bariera implicita la sfarsit, numai daca nu se utilizeaza „nowait”.Exemplu: adunarea a doi vectori, primele n/2 iteratii fiind distribuite primului thread, iar restul la un al doilea thread.

Page 11: ALGORITMI PARALELI ȘI DISTRIBUIȚI ÎNDRUMAR DE LABORATOR ...€¦ · Regiunea paralela = bloc de cod care se va executa de mai multe thread-uri. La o directivă parallel un thread

Directiva single Aceasta directiva serializeaza o sectiune de cod. Codul este executat de un singur thread. Forma generala:

Directiva parallel for Se pot combina directivele de sectiune paralela si de partajarea a lucrului cu ajutorul directivei parallel for, prin care se specifica o regiune paralela care contine o singura directiva for. Forma generala:

Exemplu: iteratiile sunt repartizate in blocuri de dimensiuni egale la toate thread-urile din set.

Page 12: ALGORITMI PARALELI ȘI DISTRIBUIȚI ÎNDRUMAR DE LABORATOR ...€¦ · Regiunea paralela = bloc de cod care se va executa de mai multe thread-uri. La o directivă parallel un thread

Exemplu:Avem 4 matrici, A,B,C,D pe care vrem sa le adunam astfel: X=A+B, Y=C+D si la final Z=X+Y.Pentru ca acest program sa se execute intr-un timp cat mai scurt, vom paraleliza cele 2 operatii de adunare: X=A+B si Y=C+D.#pragma omp parallel sections private(i,j) shared(A,B,C,D,X,Y){#pragma omp sectionfor(i=0; i<n; ++i) for(j=0; j<n; ++j) X[i][j] = A[i][j]+B[i][j];

#pragma omp sectionfor(i=0; i<n; ++i) for(j=0; j<n; ++j) Y[i][j] = C[i][j]+D[i][j];}iar la final adunam cele 2 rezultate:for(i=0; i<n; ++i) for(j=0; j<n; ++j) Z[i][j] = X[i][j]+Y[i][j];

Inmultirea a doua matrici.Presupunem a si b de tip Mn*n .Declaram a, b si c de tip double si c=a*b.Algoritmul secvential de inmultire a doua matrici este:1. for [ i = 0 to n-1 ]2. for [j = 0 to n-1 ]3. { c[i, j] = 0.0 ;4. for [k = 0 to n-1]5. c[i,j] = c[i, j] + a[i, k] * b[k, j];6. }Definitie: doua operatii se pot executa in paralel daca sunt independente.Fie ,, o " o operatie.RS(o) este multimea variabilelor utilizate in o si nemodificate de o.WS(o) este multimea variabilelor modificate de o (si eventual utilizate).Doua operatii o1 si o2 sunt independente daca:a) WS(o1) ? WS(o2) = ?b) RS(o1) ? RS(o2) = ?Ex.Instructiunea o : x = y + x + zRS(o) = {y , z}WS(o) = {x}Obs. Variabilele utilizate sunt cele care apar in partea dreapta a atribuirii.Revenind la exemplul de mai sus, cel cu inmultirea matricelor, observam ca pentru linia 5 de cod avem:RS(5) = {a[i, k] , b[k, j]} iar WS(5) = {c[i, j]}Notatie: ,, co ,, pentru a simula executia paralela. co - proces

Page 13: ALGORITMI PARALELI ȘI DISTRIBUIȚI ÎNDRUMAR DE LABORATOR ...€¦ · Regiunea paralela = bloc de cod care se va executa de mai multe thread-uri. La o directivă parallel un thread

co [k=0 to n-1]c[i, j] = c[i, j] + a[i, k]*b[k, j]ocInsa se poate observa ca operatiile de la randul 5 nu sunt independente deoarece:WS(5, i0) ? WS(5, j0) = {c[i, j]}Atunci operatiile care pot fi paralelizate sunt:co [ i = 0 to n-1 ] (se vor calcula in paralel liniile matricii c)for [j = 0 to n-1 ]{ c[i, j] = 0.0 ;for [k = 0 to n-1]c[i,j] = c[i, j] + a[i, k] * b[k, j];}Adica: procesul 0 va calcula linia 0, ... procesul i va calcula linai i ,...procesul n-1 va calcula linia n-1.Sau presupunem ca avem n2 procese care fiecare calculeaza un element al matricii c.co [ i = 0 to n-1 , j = 0 to n-1 ]{ c[i, j] = 0.0 ;for [k = 0 to n-1]c[i,j] = c[i, j] + a[i, k] * b[k, j];}

Un alt exemplu este reprezentat de înmulțirea a două matrici:/* * Sample program to test runtime of simple matrix multiply * with and without OpenMP on gcc-4.3.3-tdm1 (mingw) * (c) 2009, Rajorshi Biswas*/#include <stdio.h>#include <stdlib.h>#include <string.h>#include <time.h>#include <omp.h>int main(){ int i,j,k,n; //n=matrix dimensions (n*n) int num_threads; // number of threads int t1, t2; double **A; // matrix 1 = A double **B; // matrix 2 = B double **M; // matrix 3 = A * B double temp; double start, end;printf ("matmul - matrix multiplicationn");printf("Threads: ");scanf("%d",&num_threads);printf("N: ");scanf("%d",&n); // simple OpenMP construction #pragma omp parallel { t2 = omp_get_num_threads(); t1 = omp_get_thread_num();

Page 14: ALGORITMI PARALELI ȘI DISTRIBUIȚI ÎNDRUMAR DE LABORATOR ...€¦ · Regiunea paralela = bloc de cod care se va executa de mai multe thread-uri. La o directivă parallel un thread

printf("Hello from thread %d of %dn", t1, t2); } // allocate dynamic memory for the each matrix A = (double **)malloc( sizeof(double*) * n); B = (double **)malloc( sizeof(double*) * n); M = (double **)malloc( sizeof(double*) * n); for(i=0; i<n; ++i) { A[i] = malloc( sizeof(double) * n ); B[i] = malloc( sizeof(double) * n ); M[i] = malloc( sizeof(double) * n ); } // populating array with random values srand( time(NULL) ); for(i=0; i<n; ++i) { for(j=0; j<n; ++j) { A[i][j] = (1.0 * rand() / RAND_MAX); B[i][j] = (1.0 * rand() / RAND_MAX); } } // calculate the product of two matrices without OpenMP printf("Calculation without OpenMP..."); fflush(stdout);/*************************************************/ start = omp_get_wtime(); for(i=0; i<n; ++i) { for(j=0; j<n; ++j) { temp = 0; for(k=0; k<n; ++k) { temp += A[i][k] * B[k][j]; } M[i][j] = temp; } } end = omp_get_wtime();/*************************************************/ printf(" took %f seconds.n", end-start); printf("Calculation without OpenMP... %d threads...", num_threads); fflush(stdout);/*************************************************/ omp_set_num_threads(num_threads); start = omp_get_wtime();#pragma omp parallel for private(i, j, k, temp) shared(A, B, M) for(i=0; i<n; ++i) { for(j=0; j<n; ++j) { temp = 0; for(k=0; k<n; ++k) { temp += A[i][k] * B[k][j]; } M[i][j] = temp; } } end = omp_get_wtime();/*************************************************/ printf(" took %f seconds.n", end-start);

Page 15: ALGORITMI PARALELI ȘI DISTRIBUIȚI ÎNDRUMAR DE LABORATOR ...€¦ · Regiunea paralela = bloc de cod care se va executa de mai multe thread-uri. La o directivă parallel un thread

return 1;}

Desfăşurarea lucrării:1. Se copiază exemplele prezentate şi se vor analiza rezultatele prin repetarea execuției2. Se vor extinde instanțierile existente şi se va face o analiză a rezultatului

Page 16: ALGORITMI PARALELI ȘI DISTRIBUIȚI ÎNDRUMAR DE LABORATOR ...€¦ · Regiunea paralela = bloc de cod care se va executa de mai multe thread-uri. La o directivă parallel un thread

LABORATORUL NR.3Implementare fire de execuție

Scop: Înșelegerea și implementarea firelor de execuţie

Consideraţii teoretice:Fire de executare

Considerăm că lucrăm pe un calculator cu np 1 procesoare. Ne îndreptăm atenția supra modului în

care putem lansa în executare mai multe fire de executare (procese).Un fir de executare este un program secvențial, ce poate fi executat concurent cu alte fire.

Crearea firelor de executare

Clasa Thread

Un prim mod de a crea şi lansa în executare fire este oferit de clasa Thread din pachetul java.lang:public class Thread extends Object implements Runnable

Crearea firului se face astfel:Thread T = new Thread();

După creare, un fir este lansat în executare prin invocarea metodei run. Aceasta este o metodă a clasei Thread şi nu prevede vreo acțiune; de aceea ea trebuie redefinită conform dorințelor programatorului.

Când metoda run se termină, se termină şi firul care a invocat-o.Lansarea în executare a unui fir face ca acesta să fie executat concurent cu firul care l-a lansat. De aceea invocăm metoda run prin start.Exemplul 1. class Tip extends Thread { int k; boolean b=true;

public void run() { while(b) IO.write(" " + k++); IO.writeln("Thread"); }

public void terminare() { b=false; }}

class P1 { public static void main(String[] w) Tip Ob = new Tip(); Ob.start(); while ( IO.readch() != '\n' ); Ob.terminare(); IO.writeln("main"); }}

Dacă în loc de Ob.start() am fi folosit Ob.run(), programul ar rula la infinit (programul principal aşteaptă ca obiectul Ob să-şi termine acțiunea) !!!Interfața RunnableO a doua modalitate de a crea şi lansa în executare fire este de a folosi interfața Runnable, ce apare în

Page 17: ALGORITMI PARALELI ȘI DISTRIBUIȚI ÎNDRUMAR DE LABORATOR ...€¦ · Regiunea paralela = bloc de cod care se va executa de mai multe thread-uri. La o directivă parallel un thread

pachetul java.lang şi anunță numai metoda run.class Execute implements Runnable { . . . public void run() { . . . } . . .}

class C { . . . Execute Ob = new Execute(...); Thread T = new Thread(Ob); // firul este creat T.start(); // firul este pornit . . .}

Exemplul 2. class Execute implements Runnable { int k; boolean b=true; public void run() { while (b) IO.write(" " + k++); IO.writeln("Thread"); } public void terminate() { b = false; }}

class RunThread { public static void main(String[] arg) { Execute Ob = new Execute(); Thread T = new Thread(Ob); T.start(); while ( IO.readch() != '\n'); Ob.terminate(); IO.writeln("main"); }}

Modelul aleatorFiecare proces execută repetat următoarele acțiuni:1) alege aleator un proces "liber";2) execută un timp aleator instrucțiuni ale sale.

Modelul este independent de numărul real de procesoare.

Un prim mod de sincronizare

Dacă dintr-un fir am lansat alt fir prin intermediul obiectului Ob şi dorim să aşteptăm ca acesta să-şi termine acțiunea, vom invoca metoda join a clasei Thread.

Exemplul 3. class Tip extends Thread { static int k=1; public void run() { for (int i=0; i<500; i++) IO.write(k+" ");

Page 18: ALGORITMI PARALELI ȘI DISTRIBUIȚI ÎNDRUMAR DE LABORATOR ...€¦ · Regiunea paralela = bloc de cod care se va executa de mai multe thread-uri. La o directivă parallel un thread

}}

class P2 { public static void main (String[] arg) throws InterruptedException { Tip Ob = new Tip(); Ob.start(); Thread.sleep(50); // executarea este amanata 50 milisec. Ob.k = 2; Ob.join(); IO.writeln("***"); }}

Observație. Firul care lansează un nou fir nu are drepturi suplimentare: de exemplu se poate termina înaintea celui pe cate îl lansează.

Metoda arborelui binar Această metodă are o largă aplicabilitate în calculul paralel.Dorim să calculăm suma elementelor tabloului a=(a0,...,an-1).Putem presupune că n=2m pentru un anumit m, adăugând zerouri la dreapta.Primul pas constă în dublarea lungimii vectorului, prin mutarea elementelor pe pozițiile an,..,a2n-1.

Acum putem considera că elementele sunt noduri ale unui arbore binar complet.Observații:

- vârfurile apar pe nivelurile 0,1, ..., m-1;- vârfurile de pe nivelurile 0,1, …,m-2 au exact 2 fii, iar cele de pe nivelul m-1 sunt frunze;- vârfurile de pe nivelul i sunt 2i..2i+1-1; fiii vârfului i sunt 2i şi 2i+1.

Al doilea pas constă în traversarea bottom-up a arborelui. Pe fiecare nivel, calculele se fac în paralel.

Pasul 1.for i=0,n-1 in parallel do an+i ai {1}endfor

Pasul 2.

for k=m-1,0,-1 do for i=2k,2k+1-1 in parallel {2} ai a2i + a2i+1 endforendfor

Teoretic, folosim n procesoare. Timpul este de ordinul O(m) = O(log n), deoarece ciclul for interior necesită un timp constant şi este executat de m ori.

Programul Java corespunzător este următorul:class Type1 extends Thread { static int n; int i; public Type1(int i) { this.i = i; } public void run() { BinTree.a[n+i] = BinTree.a[i]; }}

class Type2 extends Thread {

Page 19: ALGORITMI PARALELI ȘI DISTRIBUIȚI ÎNDRUMAR DE LABORATOR ...€¦ · Regiunea paralela = bloc de cod care se va executa de mai multe thread-uri. La o directivă parallel un thread

int i; public Type2(int i) { this.i = i; } public void run() { BinTree.a[i] = BinTree.a[2*i]+BinTree.a[2*i+1]; }}

class BinTree { static int[] a = new int[32]; static int n; public static void main(String[] arg) { int m=0, i; IO.write("m= "); m = (int) IO.read(); //m<=5 int n=1; for (i=0; i<m; i++) n *= 2; IO.write("Numbers : "); for (i=0; i<n; i++) a[i] = (int) IO.read(); // ------------------------------------------

Type1[] Ob1 = new Type1[n]; Type1.n = n; for (i=0; i<n; i++) Ob1[i] = new Type1(i); for (i=0; i<n; i++) Ob1[i].start(); try { for (i=0; i<n; i++) Ob1[i].join(); } catch (InterruptedException e) { } // ------------------------------------------

Type2[] Ob2 = new Type2[n]; int p = n/2, u = n; for (int k=m-1; k>=0; k--) { for (i=p; i<u; i++) Ob2[i] = new Type2(i); for (i=p; i<u; i++) Ob2[i].start(); try { for (i=p; i<u; i++) Ob2[i].join(); } catch(InterruptedException e) { } for (i=p; i<u; i++) IO.write(a[i]+" "); IO.writeln(); p = p/2; u = u/2; } IO.writeln("Suma = "+a[1]); }}

Problema grădinilor ornamentale (atribuirii multiple)class C { static int total; int temp;

synchronized void compute() { temp=total; temp++; total=temp; }}

class Type extends Thread { static C Ob; int i; Type(int i) { this.i=i; }

Page 20: ALGORITMI PARALELI ȘI DISTRIBUIȚI ÎNDRUMAR DE LABORATOR ...€¦ · Regiunea paralela = bloc de cod care se va executa de mai multe thread-uri. La o directivă parallel un thread

void delay() { try { Thread.sleep( (int) (100 * Math.random()) ); } catch(InterruptedException e) { } }

public void run() { for (int j=1; j<=10; j++) { delay(); Ob.compute(); IO.write(" "+i); } }}

class Garden { public static void main(String[] s) { Type.Ob = new C(); Type[] T = new Type[5]; for (int i=0; i<5; i++) T[i] = new Type(i); for (int i=0; i<5; i++) T[i].start(); try { for (int i=4; i>=0; i--) T[i].join(); } catch (InterruptedException e) { } IO.writeln(); IO.writeln("Total = " + C.total); }

}

Desfăşurarea lucrării:1. Se copiază exemplele prezentate şi se vor analiza rezultatele prin repetarea execuției2. Se vor extinde instanțierile existente şi se va face o analiză a rezultatului

Page 21: ALGORITMI PARALELI ȘI DISTRIBUIȚI ÎNDRUMAR DE LABORATOR ...€¦ · Regiunea paralela = bloc de cod care se va executa de mai multe thread-uri. La o directivă parallel un thread

LABORATORUL NR.4Implementare semafoare

Scop: 1. Înțelegerea conceptului de semafor2. Implementarea mecanismului privind semafoarele

Consideraţii teoretice:Semafoare

Noţiunea de semafor

Una dintre modalităţile de a rezolva mai simplu problema excluderii reciproce, precum şi alteprobleme din programarea concurentă, este utilizarea semafoarelor.

Noţiunea de semafor a fost introdusă în 1968 de către Dijkstra.Semaforul este un tip de date, caracterizat deci prin valorile pe care le poate lua şi operaţiile î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 mulţime de blocare Bs, iniţial vidă.

Operaţiile definitorii pentru semafoare sunt următoarele:1) wait(s) : dacă valoarea semaforului s este diferită de zero (pozitivă), atunci se micşorează cu o unitate

această valoare şi se trece mai departe; în caz contrar procesul care execută această operaţie 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 mărită cu o unitate.Operaţiile wait şi respectiv signal mai sunt notate cu P şi respectiv V (iniţialele cuvintelor din limba

olandeză semnificând testare şi incrementare).Este important de precizat că fiecare dintre cele două operaţii este considerată o acţiune atomică (se

mai spune că este vorba de primitive). Aceasta înseamnă că de exemplu pentru operaţia wait eventualasuccesiune comparare + decrementare este indivizibilă. Analog, în cazul operaţiei signal succesiunilecomparare+deblocare şi comparare+incrementare sunt indivizibile. Este uşor de anticipat că aceasta vapermite rezolvarea simplă a problemei excluderii reciproce.

Mai precizăm că în timp ce două operaţii asupra aceluiaşi semafor se execută prin excludere reciprocă,pentru două operaţii asupra unor semafoare distincte o suprapunere a executării lor este posibilă.

În cele ce urmează dăm o imagine plastică a modului de funcţionare a unui semafor.Considerăm o parcare pentru maşini prevăzută cu o intrare şi o ieşire. Activităţile din parcare sunt

dirijate de un paznic, care autorizează intrările (respectiv ieşirile) maşinilor în (respectiv din) parcare şi ţineminte numărul s de locuri libere. În continuare arătăm că activitatea paznicului este similară cu funcţionareaunui semafor.

Fiecare şofer, înainte de a încerca să intre sau să iasă din parcaj, anunţă mai întâi paznicul. Acesta, fiindun om obişnuit, nu poate discuta simultan cu mai multe persoane, deci niciun şofer nu se poate adresapaznicului în intervalul de timp în care acesta dialoghează cu un alt şofer (asigurându-se astfel excludereareciprocă).

Există două modalităţi prin care paznicul discută cu şoferii, corespunzătoare celor două primitive

Page 22: ALGORITMI PARALELI ȘI DISTRIBUIȚI ÎNDRUMAR DE LABORATOR ...€¦ · Regiunea paralela = bloc de cod care se va executa de mai multe thread-uri. La o directivă parallel un thread

asupra semafoarelor: - wait(s) : Un şofer anunţă paznicul că doreşte să intre în parcare. Paznicul verifică dacă există locuri

libere. În caz afirmativ, şoferului i se permite accesul, maşina ocupă unul dintre locurile libere, valoarea lui s semicşorează cu o unitate şi dialogul se încheie. În caz contrar, paznicul comunică şoferului că nu poate intradeoarece nu există locuri libere, dar că a notat numărul maşinii, invitându-l să fie gata să intre în parcar atuncicând îi va face semn; dialogul se încheie.

- signal(s) : Un şofer doreşte să iasă din parcaj şi începe dialogul cu paznicul prin a îl informa de aceastăintenţie. Dacă există şoferi care aşteaptă să intre în parcare, atunci paznicul îi permite să iasă din parcareşoferului care a iniţiat dialogul, concomitent (pentru operativitate) făcând semn să intre unuia dintre şoferiiaflaţi pe lista de aşteptare a paznicului; dialogul se încheie. În caz contrar, paznicul autorizează părăsireaparcajului de către şoferul ce a iniţiat dialogul, măreşte cu o unitate valoarea lui s şi încheie dialogul.

Pe lângă cele două primitive prezentate mai sus, singura operaţie permisă (şi necesară) asuprasemafoarelor este atribuirea unei valori iniţiale.

Recapitulând, descriem în pseudocod tipul semafor:Valori posibile: numere naturale.

Declararea unei variabile de tip semafor:var s:semaphore;

Operaţii permise:1) initial(s,s0)2) wait(s) :

if s>0then ss-1else procesul curent este blocat

3) signal(s) :

if există procese blocatethen unul dintre ele este deblocatelse ss+1

Operaţiile wait şi signal asupra aceluiaşi semafor se execută prin excludere reciprocă.

InvarianţiPentru un semafor s oarecare, următoarele relaţii sunt invariante:

s>=0 (1)s=s0+#(signal)-#(wait) (2)unde s0 este valoarea iniţială a semaforului, #(wait) indică de câte ori s-a trecut complet de semafor (nu senumără deci procesele temporar blocate la semafor), iar #(signal) este numărul operaţiilor signal executate.Relaţiile (1) şi (2) sunt invariante în sensul că sunt îndeplinite la orice moment de timp.

Să verificăm de exemplu relaţia (2). Ea este satisfăcută la iniţializarea semaforului. Fie un moment detimp oarecare la care relaţia este îndeplinită şi să considerăm prima operaţie ulterioară asupra semaforului.Distingem cazurile:

- se execută o operaţie wait: dacă s=0 atunci cantităţile ce intervin în relaţie rămân neschimbate; dacă s>0

atunci s scade cu o unitate, iar #(wait) creşte cu o unitate;- se execută o operaţie signal: dacă există procese blocate de semaforul s atunci #(signal) şi #(wait) cresc cu

o unitate, valoarea lui s rămânând astfel neschimbată; în caz contrar, #(signal) şi s cresc cu o unitate.Excluderea reciprocăPentru realizarea excluderii reciproce, vom plasa secţiunea critică între "parantezele" wait şi signal:

Page 23: ALGORITMI PARALELI ȘI DISTRIBUIȚI ÎNDRUMAR DE LABORATOR ...€¦ · Regiunea paralela = bloc de cod care se va executa de mai multe thread-uri. La o directivă parallel un thread

wait(s); SC

signal(s); SNC

Semaforul s trebuie iniţializat cu 1: dacă ar fi iniţializat cu 0, în momentul în care oricare dintre procese ar încerca să intre în secţiunea sa critică, el ar fi suspendat la executarea primitivei wait(s), contrazicându-se astfel condiţia de competiţie constructivă; dacă ar fi iniţializat cu o valoare mai mare decât 1, atunci două procese care doresc amândouă să intre în secţiunea lor critică vor reuşi acest lucru, contrazicându-se condiţia de excludere reciprocă.

În continuare vom considera numai momentele de timp când toate procesele sunt, într-o distribuţie oarecare, în secţiunile 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 instrucţiunea signal, el esteconsiderat deja ca fiind în secţiunea necritică şi nu urmând să intre în aceasta.

Pentru a demonstra corectitudinea algoritmului, vom arăta că: s+#(SC)=1 (3)este o relaţie invariantă, unde #(SC) este numărul de procese ce se află în secţiunea lor critică.

Ţinând cont de relaţia invariantă (2) şi de faptul că s0=1, este suficient să demonstrăm că: #(SC)=#(wait)-#(signal).

Această ultimă relaţie este iniţial adevărată şi rămâne adevărată atât la intrarea oricărui proces în secţiunea critică (se măresc concomitent #(SC) şi #(wait)), cât şi la intrarea oricărui proces în secţiunea sa necritică (se micşorează #(SC) şi se măreşte #(signal)).

Din relaţia (3) rezultă că este îndeplinită condiţia de excludere reciprocă (#(SC)1), precum şi cea de competiţie constructivă (dacă nici un proces nu este în secţiunea critică, atunci s=1 şi deci unul dintre ele va intra sigur). Condiţia de conexiune liberă este şi ea evident satisfăcută.Excluderea reciprocăPentru realizarea excluderii reciproce, vom plasa secţiunea critică între "parantezele" wait şi signal:

wait(s); SC

signal(s); SNC

Semaforul s trebuie iniţializat cu 1: dacă ar fi iniţializat cu 0, în momentul în care oricare dintre procesear încerca să intre în secţiunea sa critică, el ar fi suspendat la executarea primitivei wait(s), contrazicându-seastfel condiţia de competiţie constructivă; dacă ar fi iniţializat cu o valoare mai mare decât 1, atunci douăprocese care doresc amândouă să intre în secţiunea lor critică vor reuşi acest lucru, contrazicându-se condiţiade excludere reciprocă.

În continuare vom considera numai momentele de timp când toate procesele sunt, într-o distribuţieoarecare, în secţiunile lor critice, necritice sau sunt blocate (deci nici unul dintre procese nu este în curs deexecutare a primitivelor wait şi signal); de exemplu după ce un proces a executat instrucţiunea signal, el esteconsiderat deja ca fiind în secţiunea necritică şi nu urmând să intre în aceasta.

Pentru a demonstra corectitudinea algoritmului, vom arăta că: s+#(SC)=1 (3)este o relaţie invariantă, unde #(SC) este numărul de procese ce se află în secţiunea lor critică.

Ţinând cont de relaţia invariantă (2) şi de faptul că s0=1, este suficient să demonstrăm că:#(SC)=#(wait)-#(signal).

Page 24: ALGORITMI PARALELI ȘI DISTRIBUIȚI ÎNDRUMAR DE LABORATOR ...€¦ · Regiunea paralela = bloc de cod care se va executa de mai multe thread-uri. La o directivă parallel un thread

Această ultimă relaţie este iniţial adevărată şi rămâne adevărată atât la intrarea oricărui proces însecţiunea critică (se măresc concomitent #(SC) şi #(wait)), cât şi la intrarea oricărui proces în secţiunea sanecritică (se micşorează #(SC) şi se măreşte #(signal)).

Din relaţia (3) rezultă că este îndeplinită condiţia de excludere reciprocă (#(SC) 1), precum şi cea decompetiţie constructivă (dacă nici un proces nu este în secţiunea critică, atunci s=1 şi deci unul dintre ele vaintra sigur). Condiţia de conexiune liberă este şi ea evident satisfăcută.

O implementare simplă a semafoarelor

O primă implementare este oferită de următoarea 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(); }}

Observaţii: - constructorul clasei realizează, odată cu crearea unui obiect, şi iniţializarea 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 operaţiile wait(sem) şi signal(sem) din

modelul general propus de Dijkstra vor trebui înlocuite respectiv prin sem.W() şi sem.S().

Propoziţie. Fie s un semafor (obiect de tipul clasei Semafor). Există procese blocate prin metoda Wîn mulţimea Ws dacă şi numai dacă val<0; în acest caz, numărul lor este -val.

Afirmaţia este evident adevărată la momentul iniţial (crearea semaforului s). Când 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; numărul firelor blocate este -val.

Când 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++, corespunzător 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 deexecutare". Drept urmare, fiecare dintre firele blocate prin W are şansa ca la un moment dat să-şi reiaactivitatea şi să termine de executat metoda W.

Exemplul 1. Problema grădinilor ornamentale.

Reluăm problema grădinilor ornamentale. În plus vom crea un bazin de fire de executare cu numărfix de fire. Vom folosi două semafoare: ER (pentru excludere reciprocă) şi s (pentru simularea bazinului defire).

class C {

Page 25: ALGORITMI PARALELI ȘI DISTRIBUIȚI ÎNDRUMAR DE LABORATOR ...€¦ · Regiunea paralela = bloc de cod care se va executa de mai multe thread-uri. La o directivă parallel un thread

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); }}

Problema Producător - ConsumatorReluăm aceată problemă, rezolvată anterior cu monitoare.Fie lung lungimea benzii. Vom folosi semafoarele libere şi ocupate, a căror valoare corespunde

numărului de celule libere, respectiv ocupate de pe bandă; corespunzător, ele vor fi iniţializate cu lung(lungimea benzii), respectiv 0.

Să observăm că este verificată relaţia libere+ocupate=lung; aceasta nu permite însă folosirea unuisingur semafor!

Procesul Prod

for ch='a','z' wait(libere); wait(ExRec); pune(ch); write(" P" + ch); signal(ExRec); signal(ocupate)end

Procesul Cons

repeat wait(ocupate);

Page 26: ALGORITMI PARALELI ȘI DISTRIBUIȚI ÎNDRUMAR DE LABORATOR ...€¦ · Regiunea paralela = bloc de cod care se va executa de mai multe thread-uri. La o directivă parallel un thread

wait(ExRec); ia(ch); write(" P" + ch); signal(ExRec); signal(libere)until ch='z' La o masă rotundă stau nf filozofi chinezi, iar între fiecare doi filozofi vecini se află un beţişor.

Fiecare filozof execută în mod repetat secvenţa de acţiuni (gândeşte, mănâncă), pentru intervalevariabile de timp:

ia beţişorul din stânga; ia beţişorul din dreapta; foloseşte beţişoare pentru a mânca din castronul cu orez din centrul mesei; pune pe masă beţişorul din stânga; pune pe masă beţişorul din dreapta.

Răspunsul la întrebarea "Ce se poate întâmpla rău" are două aspecte: doi filozofi vecini pot lua amândoi beţişorul dintre ei, deci trebuie asigurată excluderea reciprocă asupra

beţişoarelor; se poate ajunge la blocare totală (deadlock) în situaţia în care toţi filozofii au ridicat beţişorul din stânga

lor; trebuie deci evitată această situaţie.

Fiecărui filozof i îi asociem un proces Fi. Pentru rezolvarea excluderii reciproce vom asocia fiecărui beţişor i un semafor binar bi, iniţializat cu 1;

încercarea de ridicare a unui beţişor corespunde interogării semaforului asociat lui.Pentru evitarea blocării totale introducem semaforul AccesLiber, care va memora în permanenţă

numărul de filozofi cărora le dăm voie să încerce să mănânce. Iniţializarea 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 gândeşte wait(AccesLiber); wait(b[i]); wait(b[(i mod nf)+1]); write(" M" + i); filozoful i mănâncă write(" G" + i); signal(b[i]); signal(b[(i mod nf)+1]); signal(AccesLiber)until false

Mesajul de început al activităţii de a mânca trebuie inserat imediat după ce se trece de semafoarele carecontrolează posibilitatea de a fi ridicate beţişoarele din stânga şi dreapta filozofului.

Mai puţin evident este că mesajul de început al activităţii de a gândi trebuie inserat imediat înainte (şinu după) semnalările repunerii beţişoarelor pe masă. Dacă mutăm acest mesaj după operaţiile signal cecorespund repunerii beţişoarelor pe masă, lucrurile se vor desfăşura corect din punctual de vedere alcerinţelor problemei, dar incorect din punctul de vedere al semnalării acţiunilor; într-adevăr, pe ecran poate săapară secvenţa:G4 G3 G1 G5 G2 M1 M2 G1 ...deoarece mesajul "G1 " apare după ce filozoful 1 repune beţişoarele pe masă, între timp filozoful 2 începând sămănânce.

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;

Page 27: ALGORITMI PARALELI ȘI DISTRIBUIȚI ÎNDRUMAR DE LABORATOR ...€¦ · Regiunea paralela = bloc de cod care se va executa de mai multe thread-uri. La o directivă parallel un thread

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ă numărul de permisiunide a intra în secţiunea critică (de a accesa resurse comune). Încercarea de a accesa resursele comune se faceprin invocarea metodei acquire(), iar renunţarea la această operaţie se face prin invocarea metodeirelease(); cele două metode corespund lui wait şi signal din definiţia general a semafoarelor.

Un semafor iniţializat 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 aplicaţia concretă.Spre deosebire de alte mecanisme de programare concurentă, semafoarele (care nu au noţiunea de

posesie a unui lacăt) au proprietatea că "lacătul" poate fi deschis de un alt fir (nu există restricţia cametodele acquire şi release asupra aceluiaşi semafor să apară în acelaşi fir).

Cei doi constructori ai clasei sunt:

public Semaphore(int n)iniţializează la n valoarea semaforului şi nu asigură fairness la deblocarea firelor prin release(). Dacăn<0, sunt necesare invocări release() înainte ca o invocare să realizeze accesul la secţiunea critică.

public Semaphore(int n, boolean fair)

Page 28: ALGORITMI PARALELI ȘI DISTRIBUIȚI ÎNDRUMAR DE LABORATOR ...€¦ · Regiunea paralela = bloc de cod care se va executa de mai multe thread-uri. La o directivă parallel un thread

diferă de constructorul anterior prin aceea că dacă al doilea argument este true, la deblocare esteasigurată disciplina de coadă. Această disciplină este însă relativă la puncte de executare specifice dinmetodele acquire şi release, ceea ce poate conduce la ordini întrucâtva diferite de cele aşteptate;important este însă că în mod sigur este evitată situaţia de "starvation" (amânare infinită), adicăeşuarea continuă de a accesa o resursă.

Prezentăm în continuare principalele metode ale clasei.public void acquire() throws InterruptedException

Dacă valoarea semaforului este pozitivă, această valoare este decrementată cu o unitate. În caz contrar,firul este blocat până când un alt fir execută release() şi firul curent este cel ales să treacă în starea gatade executare. Este echivalentul lui wait.Excepţia este lansată dacă firul este în starea "întrerupt" la intrarea în metodă sau dacă este întrerupt întimp ce aşteaptă permisiunea de a intra în secţiunea critică; în aceste cazuri firul iese din starea"întrerupt".

public boolean tryAcquire()Întoarce rezultatul încercării de a obţine o permisiune. Dacă este posibil să obţină o permisiune, oobţine chiar dacă semaforul urmează politica de fairness (deci o obţine înaintea altor fire care suntblocate) şi decrementează valoarea semaforului; în acest caz valoarea întoarsă este true. Dacă nu sepoate obţine o permisiune, metoda întoarce valoarea false şi firul care a invocat-o îşi continuăactivitatea.

public void release()Eliberează o permisiune, incrementând numărul permisiunilor cu o unitate. Dacă există fire ce aşteaptăo permisiune, este ales unul dintre ele; el capătă permisiunea (deci valoarea semaforului scade cu ounitate) şi trece în starea gata de executare. Este echivalentul lui signal.

public int availablePermits()Întoarce valoarea curentă (numărul curent de permisiuni) a semaforului; este folosită de obicei pentrudepanare.

Clasa Semaphore pune la dispoziţie şi metode pentru cereri/eliberări de permisiuni multiple, dintrecare unele sunt prezentate în continuare. Este recomandat să fie folosite în modul fair, pentru a nu conducela amânare infinită.

Excepţia 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 cazcontrar, firul iese din schema de programare a firelor până când fie un alt fir execută release(), firulcurent este cel ales să-şi reia activitatea şi valoarea semaforului este cel puţin egală cu p, fie un alt firîntrerupe pe cel curent. Excepţia InterruptedException este lansată dacă firul este în starea "întrerupt" la intrarea în metodă saudacă este întrerupt în timp ce aşteaptă permisiunea de a intra în secţiunea critică; în aceste cazuri firuliese din starea "întrerupt"; toate permisiunile acordate acestui fir sunt transferate altor fire careaşteaptă să obţină permisiuni, ca şi când au devenit disponibile permisiuni prin executarea unuirelease().

public boolean tryAcquire(int p) throws IllegalArgumentExceptionDacă valoarea semaforului este cel puţin p, sunt obţinute p permisiuni (vezi şi observaţia de la formafără argumente a metodei), valoarea semaforului este decrementată cu p şi rezultatul întors este true.În caz contrar, valoarea semaforului rămâne neschimbată şi rezultatul întors este false.

public void release(int p) throws IllegalArgumentExceptionIncrementează cu p valoarea semaforului. Cât timp valoarea semaforului este strict pozitivă şi există fireblocate la semafor, se execută următoarele acţiuni:

Page 29: ALGORITMI PARALELI ȘI DISTRIBUIȚI ÎNDRUMAR DE LABORATOR ...€¦ · Regiunea paralela = bloc de cod care se va executa de mai multe thread-uri. La o directivă parallel un thread

1. este ales unul dintre firele blocate; fie p' numărul de permisiuni de care are nevoie;2. i se atribuie min(p,p') permisiuni, cu actualizările de rigoare; dacă i s-au atribuit p' permisiuni, firul

reintră în schema de gestiune a firelor.

Observaţii: - metodele release cu şi fără argumente lucrează diferit, deci (cred că) trebuie lucrat ori cu permisiuni

unice, ori cu permisiuni multiple !- metodele tryAcquire (cu cerere de permisiune unică sau multiplă) au câte o variantă pentru timp real.

Exemplul 3. Fie s,t două tablouri cu ns şi respectiv nt elemente. Se cere să se elaboreze un programconcurent care să înscrie în s cele mai mici ns, iar în t cele mai mari nt elemente din cele două tablouri. Singuraoperaţie posibilă ce implică un element din s şi un element din t este interschimbarea lor.

Se urmează ideea de la problema Producător - Consumator cu o bandă de lungime 1.La fiecare pas, firul FirS calculează maximul valorilor din s şi îl transmite (îl pune la dispoziţia) firului

FirT. Acesta calculează minimul valorilor din t şi, dacă valoarea primită este mai mare decât minimul, se faceinterschimbare. Facem observaţia că, întrucât lungimea benzii este 1, acţiunile firelor FirS şi FirT se succed înmod repetat în această ordine.

Entităţile comune apar în clasa C. Semafoarele s1 şi s2 corespund semafoarelor libere şi ocupate dinproblema Producător - Consumator, fiind iniţializate ş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 {

Page 30: ALGORITMI PARALELI ȘI DISTRIBUIȚI ÎNDRUMAR DE LABORATOR ...€¦ · Regiunea paralela = bloc de cod care se va executa de mai multe thread-uri. La o directivă parallel un thread

while(C.continua) { C.s1.acquire(); C.ks = 0; C.maxs=C.s[0]; 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) { } }}

Observaţie. Deşi este respectată condiţia ca singura operaţie ce implică un element din s şi un elementdin t să fie interschimbarea lor, soluţia de mai sus are dezavantajul că ambele fire au acces la ambele tablouri.

Problema Cititori - Scriitori

Se consideră o carte la care au acces mai mulţi cititori şi mai mulţi scriitori. Este permis ca mai mulţi cititori săaibă acces simultan la conţinutul cărţii, dar dacă un scriitor operează asupra cărţii (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 operaţia de citire dacă şi numai dacă nici un scriitor nu este în curs de a scrie încarte;

2) un scriitor poate începe operaţia de scriere dacă şi numai dacă nici un cititor şi nici un alt scriitor nu auacces la carte.

Cu alte cuvinte, dacă cuplul (nrcit,nrscr) ţine evidenţa nunărului de cititori activi şi numărul de scriitoriactivi, valorile permise ale cuplului sunt numai:(0,0), (0,1) şi (n,0) cu n>0.

Încercările de deschidere a cărţii au şanse de reuşită doar dacă nici un scriitor nu este activ. În acestcaz ele se împart în două categorii:

a) cele de citire când există cel puţin un cititor activ;b) cele de citire când nu există cititori activi + cele de scriere.

Încercările din prima categorie trebuie satisfăcute necondiţionat.

Page 31: ALGORITMI PARALELI ȘI DISTRIBUIȚI ÎNDRUMAR DE LABORATOR ...€¦ · Regiunea paralela = bloc de cod care se va executa de mai multe thread-uri. La o directivă parallel un thread

Vom considera încercările de deschidere a cărţii din a doua categorie ca fiind egal rivale; drept urmarevom controla executarea lor printr-un semafor sem ce trebuie iniţializat cu 1, deoarece orice primă încercarede acest tip trebuie validată. Din motive de simetrie, sem va fi actualizat (prin operaţia signal) în exact aceleaşisituaţii. Semaforul sem va avea valoarea 0 dacă şi numai dacă la carte are acces cel puţin o persoană.

Semafoarele ExRec şi s sunt folosite pentru excludere reciprocă, deci sunt iniţializate cu 1. Procesele Cititori :

repeat deschide(i,citire); preia informaţia dorită inchide(i,citire); prelucrează informaţia citităuntil false

Procesele Scriitori :repeat redactează un text nou; deschide(i,scriere); introdu textul in carte; 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

Acţiunile de deschidere şi închidere pentru citire trebuie executate sub excludere reciprocă. Într-adevăr, la apelul inchide(i,citire) cu nrcit=1, după decrementarea lui nrcit nu este sigur că nrcit=0, deoareceeste posibil ca între timp valoarea "resursei comune" nrcit să se fi modificat printr-un apel deschide(j,citire).De aceea am folosit semaforul ExRec, iniţializat cu 1.

Observaţie. Dacă am include "pentru siguranţă" şi operaţia de închidere pentru scriere întrewait(ExRec) şi signal(ExRec), se poate ajunge la blocare globală în următoarea situaţie:

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.

Notăm prin nrscr numărul de scriitori activi. În cele ce urmează vom înţelege prin stare a programuluitripletul (nrscr,nrcit;sem). Starea iniţială este (0,0;1).

Vom arăta că singurele stări posibile sunt următoarele:(0,0;1), (1,0;0) şi (0,n;0) cu n>0.

Dacă suntem în starea (0,0;1):

Page 32: ALGORITMI PARALELI ȘI DISTRIBUIȚI ÎNDRUMAR DE LABORATOR ...€¦ · Regiunea paralela = bloc de cod care se va executa de mai multe thread-uri. La o directivă parallel un thread

- poate avea loc o deschidere pentru citire sau scriere, ajungându-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 contradicţie 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 stările (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, trecându-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 stările (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. Prezentăm programul Java corespunzător 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) 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 + "("); } }

Page 33: ALGORITMI PARALELI ȘI DISTRIBUIȚI ÎNDRUMAR DE LABORATOR ...€¦ · Regiunea paralela = bloc de cod care se va executa de mai multe thread-uri. La o directivă parallel un thread

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) { } }}

Observaţie. Metodele tryAcquire (cu cerere de permisiune unică sau multiplă) au câte o variantăpentru timp real.

Bariere

Un semafor având totdeauna valoarea zero, numit şi barieră: mai multe fire se aşteaptă unul pe altulsă îndeplinească o primă acţiune, înainte de a trece mai departe.

Prezentăm î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)

Page 34: ALGORITMI PARALELI ȘI DISTRIBUIȚI ÎNDRUMAR DE LABORATOR ...€¦ · Regiunea paralela = bloc de cod care se va executa de mai multe thread-uri. La o directivă parallel un thread

unde n reprezintă numărul de fire care sunt aşteptate să ajungă la barieră. Pentru a doua formă, acţiuneaprecizată pentru fir va fi executată după ce toate cele n fire au ajuns la barieră şi înainte ca ele să-şi reiaexecutarea.

Bariera se numeşte ciclică deoarece poate fi refolosită după ce toate firele au ajuns la barieră, punctîn care îşi reiau executarea.

Dintre metodele clasei menţionăm doar următoarea:

public int await() throws InterruptedException, BrokenBarrierExceptionfirul curent este ataşat barierei care execută această metodă şi trece în aşteptare până când toate celen fire au ajuns la barieră, reluându-şi apoi executarea. Rezultatul întors este k = al câtelea a ajuns firul labarieră, cu precizarea că 0 corespunde ultimului fir ajuns la barieră.

Exemplul 5. Sunt lansate 5 fire, cu identităţile 0,1,...,4, care execută următoarele acţiuni: într-o primă etapă îşi tipăresc de 10 ori identitatea; când toate au ajuns la barieră, se execută firul fir,

care tipăreşte "****"; cele 5 fire îşi reiau activitatea şi pentru fiecare este afişat al câtelea a ajuns la barieră; în a doua etapă se reiau activităţile de mai sus, cu deosebirea că ele îşi tipăresc de 10 ori identitatea

incrementată cu 10.

Este creată o barieră cb de tipul CyclicBarrier, ataşându-i-se firul fir. Bariera este folosită pentrurealizarea primei etape, iar apoi refolosită pentru realizarea celei de a doua etape; în acest scop este necesarca 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(); 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 {

Page 35: ALGORITMI PARALELI ȘI DISTRIBUIȚI ÎNDRUMAR DE LABORATOR ...€¦ · Regiunea paralela = bloc de cod care se va executa de mai multe thread-uri. La o directivă parallel un thread

for(int i=0; i<5; i++) new Tip(i).start(); }

}

Desfăşurarea lucrării:1. Se copiază exemplele prezentate şi se vor analiza rezultatele prin repetarea execuției2. Se vor extinde instanțierile existente şi se va face o analiză a rezultatului

Page 36: ALGORITMI PARALELI ȘI DISTRIBUIȚI ÎNDRUMAR DE LABORATOR ...€¦ · Regiunea paralela = bloc de cod care se va executa de mai multe thread-uri. La o directivă parallel un thread

LABORATORUL NR.5Implementarea şi testare algoritm de sortare paralelă

Scop: 1.

Consideraţii teoretice:Putem clasifica algoritmii de sortare după o serie de criterii. Cei mai cunoscuțialgoritmi sunt cei prin comparație, iar cel mai comun criteriu de clasificare a acestora estedupă metoda generală de lucru. Astfel avem algoritmi de sortare prin:Inserție:• Insertion sort – un algoritm de sortare eficient pe liste de intrare mici sauaproape sortate• Shellsort• Binary tree sort• Cyclesort – un algoritm cu numărul de scrieri în memorie redusSelecție:• Selectionsort – eficient pe liste de intrare mici• Heapsort – un algoritm de sortare cu timp de execuție constant• Smoothsort – inspirat de Heapsort, dezvoltat de către Edsger DijkstraAlgoritmii de sortare prin inserție şi selecție sunt în general algoritmi care nu pot fi uşor paralelizați, dar pot fi folosiți împreună cu alți algoritmi pentru a forma algoritmi de sortare hibrizi. Mai avem algoritmi de sortare prin:Partiționare:• Quicksort – unul dintre cei mai cunoscuți algoritmi de sortareIntrosort – un hibrid între Quicksort şi HeapsortInterclasare (merging):• Mergesort• Timsort – este un hibrid între Mergesort şi Insertion sortDistribuire:• BucketsortAlgoritmii prin partiționare, interclasare şi distribuire sunt algoritmii care pot fiparalelizați cel mai uşor datorită naturii acestora.Printre cei mai lenți algoritmi se numără cei prin inter-schimbare:• Bubblesort• Odd-even sort - o variație uşor îmbunătățită a Bubble-sortToți algoritmii prezentați anterior fac parte din categoria algoritmilor de sortare prin comparație. Exemple dealgoritmi de sortare care nu folosesc comparația sunt:• Radix sort• Bead sort

Radix sort spre exemplu datează din 1887, fiind folosit de către Herman Hollerith pe maşini tabelare.Modul de lucru al acestuia nu presupune comparația numerelor din lista de intrare, ci comparația cifrelor numerelor, în funcție de poziția pe care o ocupă acestea.

Page 37: ALGORITMI PARALELI ȘI DISTRIBUIȚI ÎNDRUMAR DE LABORATOR ...€¦ · Regiunea paralela = bloc de cod care se va executa de mai multe thread-uri. La o directivă parallel un thread

O altă metodă de clasificare a algoritmilor este în funcție de stabilitatea acestora. Un algoritm desortare stabil va păstra ordinea elementelor care au chei egale. Astfel dacă avem de sortat un şir dedicționare cum ar fi:{2: 'amet'} {1: 'dolor'} {1: 'sit'} {0: 'lorem'} {0: 'ipsum'}Un algoritm de sortare stabil va produce:{0: 'lorem'} {0: 'ipsum'} {1: 'dolor'} {1: 'sit'} {2: 'amet'}Păstrând astfel ordinea inițială a elementelor care au chei egale. Dintre algoritmii menționați urmează săprezentăm pe larg câțiva dintre ei, şi anume: Quicksort, Mergesort, Heapsort şi Selection sort.1.2. QuicksortQuicksort este un algoritm de sortare conceput de către C.A.R. Hoare în 1962. Înpractică este unul dintre cei mai rapizi algoritmi de sortare. Quicksort face în medieΟ(nlog (n)) comparații atunci când mărimea listei de intrare este de n elemente. De regulăeste mai rapid decât alți algoritmi Ο(nlog (n)) . În cel mai rău caz, Quicksort face Ο(n2)comparații, deşi acest caz este de obicei rar. Nu este un algoritm de sortare stabil.Quicksort se bazează pe principiul divide et impera. Algoritmul împarte lista deintrare în două sub-liste mai mici, pe care le sortează în mod recursiv, reaplicând acelaşialgoritm. Paşii efectivi sunt:• Alege un element, numit pivot, din listă.• Re-aranjează lista în aşa fel încât elementele mai mici decât pivotul vor fiîn stânga acestuia, iar elementele mai mari vor fi în dreapta. Aceastăoperație se numeşte partiționare.• Aplică recursiv algoritmul pe sub-lista cu elemente mai mici, şi pe sublistacu elemente mai mari.• Atunci când mărimea listei de intrare este 0 sau 1, nu este nevoie să maire-aplicăm algoritmul Are o complexitate de Ο(nlog (n)) . La o analiză a acestuia, îl putem descompune la fiecare pas în operațiileexecutate şi stabili astfel complexitatea sa.Operația de partiționare, care iterează o dată peste elementele listei de intrare are o complexitate de Ο(n) .În cazul favorabil, dacă pivotul selectat împarte lista în două subliste de dimensiuni aproximativ egale,înseamnă că fiecare apel recursiv va procesa jumătate din datele de intrare inițiale. În consecință vom faceΟ(logn) apeluri până când vom ajunge la o listă de dimensiunea 1, pe care, evident, nu mai este nevoie să osortăm. Înseamnă că înălțimea arborelui format de către apelurile recursive va fi Ο(logn) . Dar apelurilesituate la acelaşi nivel al arborelui nu vor procesa aceeaşi parte a listei inițiale, deci fiecare nivel va avea Ο(n)comparații. Astfel algoritmul final foloseşte Ο(nlog (n)) comparații.O altă metodă de analiză este să folosim o relație de recurență, unde T (n) reprezintă timpul necesar pentrua sorta o listă de mărimea n. Pentru că un singur apel al Quicksort înseamnă Ο(n) plus încă două apeluri peliste de dimensiunea n /2 , în cazul cel mai favorabil, relația va fi: T (n)=Ο(n)+2T (n/2) , rezultând în T(n)=Ο(nlog (n)) , conform Master Theorem [6].În cazul nefavorabil, când cele două sub-liste au dimensiunea 1 şi n-1 arborele construit de către apelurilerecursive devine liniar. Astfel relația de recurență va fi:T (n)=Ο(n)+T (0)+T (n−1) care va rezulta în T (n)=Ο(n2) [7] [8].De menționat că spațiul în memorie folosit de către Quicksort este Ο(logn) ,chiar şi în cazul nefavorabil.1.3. Mergesort

Mergesort este un algoritm de sortare descoperit de către John von Neumann în 1945. La fel ca şiQuicksort, este un algoritm care se bazează pe principiul divide et impera. Acesta face atât în medie cât şi încel mai rău caz Ο(nlog (n)) comparații. În cazul Mergesort, o funcție importantă este cea de interclasare

Page 38: ALGORITMI PARALELI ȘI DISTRIBUIȚI ÎNDRUMAR DE LABORATOR ...€¦ · Regiunea paralela = bloc de cod care se va executa de mai multe thread-uri. La o directivă parallel un thread

(merge). De reținut că acest algoritm este unul stabil.Paşii pentru realizarea Mergesort sunt:• Împarte lista inițială în două sub-liste de mărimi egale. De notat este că nu folosim un pivot.• Aplică recursiv algoritmul pe fiecare dintre sub-liste.• Atunci când mărimea listei de intrare este 0 sau 1, lista este consideratăsortată.• Interclasează sub-listele sortate într-o singură listă . Algoritmul se bazează pe două principii simple pentrua câştiga performanță:• O listă mai mică va avea nevoie de mai puțini paşi pentru a fi sortată decât o lista de dimensiuni mari.• Sunt necesari mai puțini paşi pentru a construi o listă sortată din două subliste sortate, decât două sub-liste ne-sortate.Atât în cazul favorabil cât şi în cel nefavorabil, Mergesort are un timp de execuție de Ο(nlog (n)) . Relația derecurență a algoritmului va fi:

Deoarece avem nevoie de T (n/2) timp pentru a sorta fiecare sub-listă şi de ntimp pentru operația de interclasare (merge). Mai jos avem o reprezentare grafică a arboreluide recurență pentru Mergesort. Menționăm că spațiul în memorie folosit de către Mergesorteste Ο(n) .Paralelizarea algoritmilorDacă în mod secvențial nu trebuie să ținem cont în mod deosebit de nivelul derecurență al algoritmului, atunci când vrem sa-l paralelizăm, acest nivel de recurență va jucaun rol esențial. Luând Quicksort ca exemplu, va trebui să-l distribuim în mai multe procese,care vor rula în mod paralel. Această distribuire o vom face atunci când lista de intrare esteîmpărțită în două sub-liste de către elementul pivot. Astfel vom putea lansa câte un procesindependent pentru fiecare dintre cele două sub-liste.Mergând în jos pe arborele de recursivitate, cele două procese vor lansa la rândullor câte alte două sub-procese, obținând astfel un arbore de procese care se suprapunearborelui de recursivitate al algoritmului. Cu toate acestea nu vom continua să

Page 39: ALGORITMI PARALELI ȘI DISTRIBUIȚI ÎNDRUMAR DE LABORATOR ...€¦ · Regiunea paralela = bloc de cod care se va executa de mai multe thread-uri. La o directivă parallel un thread

lansăm procesepână la ultimul nivel al arborelui. Ne vom limita la a lansa procese doar pentru primele câtevaniveluri de recurență.Vom nota aceste niveluri astfel:• N0 – este nivelul rădăcină și rezultă din apelul inițial al funcției, pe listainițială de intrare.• N1 – este rezultatul a 21 apeluri ale funcției, fiecare pe câte o sub-listă.• N2 – este rezultatul a 22 apeluri, fiecare pe câte o sub-listă a niveluluianterior.18

În implementarea de Quicksort şi Mergesort dezvoltată, avem posibilitatea să lansăm în execuție 2iprocese separate la fiecare nivel, acolo unde i este indicele nivelului. Astfel, vom putea lansa un Quicksortdistribuit pe oricâte procese permite arborele de recurență, într-un caz extrem, putem chiar să lansăm câteun proces separat pentru fiecare nivel de recursivitate. Cu toate acestea, pentru experimentele realizate ne-am limitat doar la primele patru niveluri (N0 – N3), respectiv 1, 2, 4 sau 8 procese.

După ce nivelul de recursivitate va depăşi nivelul până la care facem distribuirea pe procese,algoritmul va rula în continuare în mod secvențial, apelurile recursive ale funcției având loc în cadrulprocesului părinte. Fiecare dintre aceste apeluri vor sorta lista şi vor returna rezultatele apelului părinte. Încazul proceselor lansate, acestea vor returna părintelui o structură de date de tip coadă care va conține listasortată. Această coadă nu va fi un obiect global, ci unul local, accesibil doar de procesul părinte şi cei doicopii ai săi. Procesul părinte după ce lansează cele doua procese copil urmăreşte această coadă pentrurezultate. Imediat ce obține cele două rezultate de la procesele copil le va concatena cu pivotul (dacă estecazul) şi va returna rezultatul procesului său părinte. În cazul în care acesta este deja procesul rădăcină, vareturna lista sortată.

Deoarece coada este un obiect local, aceasta va fi creată de fiecare proces părinte care urmează să lanseze sub-procese. Acest Inter-Process-Communication (IPC) se va realiza prin intermediul cozii doar între procese părinte-copil separate. Între apeluri de funcție obişnuite, comunicarea se va realiza prin returnare obişnuită a valorii.

package com.java2novice.sorting;

Page 40: ALGORITMI PARALELI ȘI DISTRIBUIȚI ÎNDRUMAR DE LABORATOR ...€¦ · Regiunea paralela = bloc de cod care se va executa de mai multe thread-uri. La o directivă parallel un thread

public class MyQuickSort { private int array[]; private int length; public void sort(int[] inputArr) { if (inputArr == null || inputArr.length == 0) { return; } this.array = inputArr; length = inputArr.length; quickSort(0, length - 1); } private void quickSort(int lowerIndex, int higherIndex) { int i = lowerIndex; int j = higherIndex; // calculate pivot number, I am taking pivot as middle index number int pivot = array[lowerIndex+(higherIndex-lowerIndex)/2]; // Divide into two arrays while (i <= j) { /** * In each iteration, we will identify a number from left side which * is greater then the pivot value, and also we will identify a number * from right side which is less then the pivot value. Once the search * is done, then we exchange both numbers. */ while (array[i] < pivot) { i++; } while (array[j] > pivot) { j--; } if (i <= j) { exchangeNumbers(i, j); //move index to next position on both sides i++; j--; } } // call quickSort() method recursively if (lowerIndex < j) quickSort(lowerIndex, j); if (i < higherIndex) quickSort(i, higherIndex); } private void exchangeNumbers(int i, int j) { int temp = array[i]; array[i] = array[j]; array[j] = temp; } public static void main(String a[]){ MyQuickSort sorter = new MyQuickSort(); int[] input = {24,2,45,20,56,75,2,56,99,53,12}; sorter.sort(input);

Page 41: ALGORITMI PARALELI ȘI DISTRIBUIȚI ÎNDRUMAR DE LABORATOR ...€¦ · Regiunea paralela = bloc de cod care se va executa de mai multe thread-uri. La o directivă parallel un thread

for(int i:input){ System.out.print(i); System.out.print(" "); } }}

Desfăşurarea lucrării:1. Se copiază exemplele prezentate şi se vor analiza rezultatele prin repetarea execuției2. Se vor extinde instanțierile existente și se va face o analiză a rezultatului

Page 42: ALGORITMI PARALELI ȘI DISTRIBUIȚI ÎNDRUMAR DE LABORATOR ...€¦ · Regiunea paralela = bloc de cod care se va executa de mai multe thread-uri. La o directivă parallel un thread

LABORATORUL NR.6Implementarea unor algoritmi

din metodele numerice

Scop: 1. Implementarea unor algoritmi numerici prin utilizarea calcului paralelConsideraţii teoretice:Metoda de optimizare Secţiunea de aur

Metoda este utilizată pentru optimizarea sistemelor monovariabile (descrise prin funcţii monovariabile)

neliniare cu sau fără restricţii.

Algoritmul:

Pasul 1 Se alege un punct arbitrar x0 şi se calculează funcţia criteriu în acest punct.

Pasul 2 Se evaluează funcţia criteriu în xx 0 . Dacă s-a obţinut o îmbunătaţire se dublează x până se

obţine un eşec.

Pasul3 Dacă nu s-a obţinut o îmbunătăţire în prima fază a pasului 2, se schimbă sensul de deplasare. Se

calculează F în xx 0 . În caz de îmbunătăţire se dublează x până se obţine un eşec.

Pasul 4 Dacă în caz că nu s-a reuşit să se obţină o îmbunătăţire în prima fază nici la pasul 2 nici la pasul 3, se

face xx 2

1 şi se reia cu pasul 2.

Pasul 5 Se reţin ultimele două puncte (eşecul şi succesul anterior eşecului) şi se ordonează (în ordine

crescătoare) 11 ba . Intervalul ),( 111 baL reprezintă restricţia problemei: minimizează F(x) cu

11 bxa .

Pasul 6 Se determină punctele 21 xsix din L1 prin relaţiile:

)(382,0 1112

111111 aba

abalax

)(618,0 1112

111112 aba

abblbx

unde: 618,12

15;

2

11

2

11

abL

l

Pasul 7 Se evaluează F(x) în punctele 21 xsix :

Dacă 2121 *)()( xxaatuncixFxF

Dacă 1121 *)()( bxxatuncixFxF

Noul subinterval în care se găseşte optimul x* va fi:

1

11222 ),(L

lLbaL

Page 43: ALGORITMI PARALELI ȘI DISTRIBUIȚI ÎNDRUMAR DE LABORATOR ...€¦ · Regiunea paralela = bloc de cod care se va executa de mai multe thread-uri. La o directivă parallel un thread

Pasul 8 În intervalul L2 se plasează punctul x3 după relaţia:

223223 lbxsaulax unde: 2

22

Ll

Pasul 9 Procesul de eliminare a intervalelor se continuă astfel că la stadiul k de căutare:

sauabalax kkkkkk )(382,01

undeabalbx kkkkkk )(618,01 1

1

2

k

LLsi

LL k

k

Pasul 10 Procedura se opreşte când este satisfăcut criteriu de convergenţă:

2|| 1 epsxx kk

Se observă că de la pasul 5 metoda devine de optimizare a unor funcţii cu restricţii de forma: 11 bxa .

Punctul de optim se obţine ca media aritmetică a punctelor ultimului interval.

Desfăşurarea lucrării:1. Metoda de mai sus va fi implementat prin construireaa unor fire de execuție la primele împărțiri

în intervale2 Se va analiza efectul utilizării a două sau patru fire de execuție.

Page 44: ALGORITMI PARALELI ȘI DISTRIBUIȚI ÎNDRUMAR DE LABORATOR ...€¦ · Regiunea paralela = bloc de cod care se va executa de mai multe thread-uri. La o directivă parallel un thread

LABORATORUL NR.7Testarea algoritmilor paraleli

Scop: 1. Realizarea unei analize privind creşterea complexității algoritmilor datorită acestei abordării şi analiza impactului asupra vitezei de execuțieConsiderente teoretice

PARALLEL COMPUTERS (- IN MOD UZUAL ) lucrează bazat pe:

• CUPLARE STRANSA,

• in general bazate pe SINCRONICITATE,

• CU UN SISTEM DE COMUNICATIE FOARTE RAPID SI FIABIL

• Spatiu unic de adresare (intr-o masura mare)

DISTRIBUTED COMPUTERS

• MAI INDEPENDENTE,

• COMUNICATIE MAI PUTIN FRECVENTA SI mai putin RAPIDA (ASINCRONA)

• COOPERARE LIMITATA

• NU EXISTA CEAS GLOBAL

•“Independent failures”

• Costuri ale comunicarii – In sisteme cu transmitere de mesaje – In sisteme cu memorie partajata

• Metrici de performanta – Timp de Executie, Suprasarcina, Accelerare, Eficienta, Cost

Timpul de executie pentru sistemele de calcul paralel.

Page 45: ALGORITMI PARALELI ȘI DISTRIBUIȚI ÎNDRUMAR DE LABORATOR ...€¦ · Regiunea paralela = bloc de cod care se va executa de mai multe thread-uri. La o directivă parallel un thread

• Ts = Timpul de executie serial: timpul masurat de la inceputul si pana la sfarsitul executiei algoritmului pe un calculator secvential. • TP= Timpul de executie paralel: timpul masurat de la inceputul executiei si pana la terminareaultimului subtask paralel.

Suprasarcina pentru sistemele de calcul paralel. • Suprasarcina (Total paralell overhead) To= diferenta dintre timpul total de lucru insumat al tuturor procesoarelor si timpul necesar celui mai rapid algoritm secvential • Timpul total de lucru insumat al tuturor procesoarelor = p*TP = timpul de calcul + timpul de comunicare + timpul de inactivitate temporara cumulat pentru toate procesoarele

Costul pentru sistemele de calcul paralel. • Cost = TP * p • Costul reflecta timpul total de lucru insumat al tuturor procesoarelor • E= Ts/Cost • Algoritm paralel optimal in cost (cost-optimal): costul rezolvarii problemei cu ajutorul algoritmului paralel este egal cu timpul de executie al celui mai rapid algoritm secvential Cost=O(Ts(n)) • Eficienta unui sistem cost-optimal este O(1)

Accelerarea pentru sistemele de calcul paralel.

• Accelerarea (Speedup) = raportul dintre timpul necesar rezolvarii unei probleme pe un procesor si timpul necesar rezolvarii in paralel pe p procesoare identice • S este o metrica a performantei algoritmului si nu a calculatorului paralel • Ts se refera la timpul de executie al celui mai bun algoritm secvential pentru respectiva problema

– De exemplu, pentru un algoritm paralel de sortare, se va raporta timpul acestuia la timpul de executia al algoritmului Quicksort secvential, nu Bubblesort secvential !

Eficienta pentru sistemele de calcul paralel. • Eficienta utilizarii calculatorului paralel: • E <=1, este 1 in cazul ideal S=p • Exemplu: adunarea a n numere pe n procesoare: • E=S/n=O(1/log n)

Legea lui Amdahl.

Legea lui Amdahl spune ca daca P este proportia din program care poate fi paralelizata si (1-P)este proportia care nu poate fi paralelizata (ramane seriala), accelerarea maximacare poate fi atinsa utilizand N procesoare este : A=1/((1− P)+P/ N) Daca facem limita cand N tinde la infinit (un numar infinit de procesoare), accelerarea maximatinde spre 1/(1-P). In practica raportul performanta/pret scade repede odata cu cresterea lui N chiar sidaca (1-P) are o valoare mica

Granularitatea. • Granularitate = dimensiunea taskurilor in care e descompusa problema paralelizabila – Granularitate fina (fine-grained): numar mare de taskuri mici – Granularitate mare (coarse-grained): numar redus de taskuri mari

Page 46: ALGORITMI PARALELI ȘI DISTRIBUIȚI ÎNDRUMAR DE LABORATOR ...€¦ · Regiunea paralela = bloc de cod care se va executa de mai multe thread-uri. La o directivă parallel un thread

• Cresterea granularitatii (scaderea numarului de procesoare) poate ajuta la imbunatatirea metricii de cost

Scalabilitatea. • Performanta unui sistem paralel depinde de dimensiunea problemei si de numarul de procesoare • Ce se intampla daca: – Creste dimensiunea problemei – Creste numarul de procesoare – Cum si cu cat se modifica performantele ? • Tipuri de scalabilitate: – Arhitecturala: cresterea debitului prin cresterea resurselor – Algoritmica: abilitatea de utilizare a resurselor mai mari

Izoeficienta. • Functia de izoeficienta: specifica rata de crestere a dimensiunii problemei necesara pentru a mentine constanta eficienta, odata cu cresterea numarului de procesoare • Sisteme nescalabile: nu se poate determina functia de izoeficienta (nu exista): eficienta nu poate fi mentinuta constanta la cresterea numarului de procesoare • Sistem eficient scalabil: functie de izoeficienta mica: ex – O(p): dimensiunea problemei trebuie sa creasca liniar cu numarul de procesoare

• Sistem greu scalabil: functie de izoeficienta mare: ex – O(2^p): dimensiunea problemei trebuie sa creasca exponential cu numarul de procesoare

Etapele descrierii unui algoritm paralel. • Identificarea partilor problemei care se pot executa in paralel • Maparea partilor pe mai multe procesoare • Impartirea datelor (de intrare, iesire si intermediare) • Gestiunea accesului la datele comune mai multor procesoare

Desfăşurarea lucrării:

1. Se alege o problemă numerică ce va fi implementată prin utilizarea calculului paralela2. Se creşte treptat numărul de fire de execuție. Prin aceasta creşte şi complexitatea algoritmului.3. Se analizează viteza de execuție pe fiecare fir în parte.4. Se analizează viteza de execuție pe întregul algoritm5. Se analizează impactul accesului la resurse comune.

Page 47: ALGORITMI PARALELI ȘI DISTRIBUIȚI ÎNDRUMAR DE LABORATOR ...€¦ · Regiunea paralela = bloc de cod care se va executa de mai multe thread-uri. La o directivă parallel un thread

BIBLIOGRAFIE

1. H. Georgescu,. Radu Boriga. Programare distribuită în Java / - Bucureşti : Editura Universității dinBucureşti, 2008

2. A. Gellert, A-C. Mitea, Algoritmi Paraleli şi Distribuiți ihttp://webspace.ulbsibiu.ro/arpad.gellert/html/APD.pdf

3. Potop Radu, Analiza Algoritmilor de Sortare pe Arhitecturi Paralele, 2011