Procesare paralela in Java 12/1/2017 Curs 8 Procesare paralela in Java
Arhitecturi Paralele
Clasificare arhitecturi paralele Clasificarea Flyn SISD - conventional SIMD - calcul vectorial MISD - scalcul sistolic MIMD – cazul general
SISD : Von Neuman Instructiuni Procesor Date Intrare Date Iesire
Arhitectura MISD B C A Flux de date de Iesire Flux de date de Intrare Instructiuni A Flux de date de Iesire Procesor A B C Instructiuni B Flux de Instructiuni C Flux de date de Intrare
Architectura SIMD Ci<= Ai * Bi A B C Flux de Instructiuni Flux de Procesor A B C Date de Intrare A Flux de Date de Iesire A Flux de Date de Iesire B Flux de Date de Intrare B Flux de Date de Iesire C Flux de Date de Intrare C Ci<= Ai * Bi
Architectura MIMD A B C Flux de Instructiuni A Flux de Instructiuni B Instructiuni C Flux de Date de Iesire A Flux de Date de Intrare A Procesor A Flux de Date de Iesire B Flux de Date de Intrare B Procesor B Flux de Date de Iesire C Flux de Date de Intrare C Procesor C
Masina MIMD cu memorie comuna Procesor A Procesor B Procesor C MEMORIE MAGISTRALA MEMORIE MAGISTRALA MEMORIE MAGISTRALA Sistem de Memorie Globala
MIMD cu memorie distribuita Canal Comunicatie Canal Comunicatie Procesor A Procesor B Procesor C MEMORIE MAGISTRALA MEMORIE MAGISTRALA MEMORIE MAGISTRALA Memorie Sistem A Memorie Sistem B Memorie Sistem C
Nivele de paralelism
Nivele de Paralelism Granularitate cod Entitate Cod Granularitate mare (nivel task) Program Granularitate medie (nivel control) Functie (thread) Granularitate fina (nivel date) Bucla Granularitate foarte fina (alegeri multiple ) Cu suport hard Task i-l Task i Task i+1 func1 ( ) { .... } func2 ( ) { .... } func3 ( ) { .... } a ( 0 ) =.. b ( 0 ) =.. a ( 1 )=.. b ( 1 )=.. a ( 2 )=.. b ( 2 )=.. + x Load
Paralelism la nivel de procese
Procesul clasic Memorie comuna Mentinuta de kernel procese processe Stiva Stiva Memorie COMUNA, segmente, pipes, Fisiere deschise sau mapate in memorie Date Date Text Text Memorie comuna Mentinuta de kernel procese processe
Definirea/Instantierea unor procese Exemple de relatii de precedenta
Procese cu unul sau mai multe fire interne de executie Proces cu un singur fir de executie Proces cu mai multe fire de executie Fire de executie Thread-uri Flux unic de instructiuni Spatiu de adrese COMUN pt fire Al procesului Flux multiplu de instructiuni
Thread-uri
Ce sunt firele de executie? Un fir de executie este o portiune de cod executabil care se poate executa in paralel (concurent) cu alte fire de executie) Registri Context Hardware/ software Cuvant stare Program Counter executare
Thread light (“proces usor”) STIVA FIRULUI Memorie Comuna DATELE FIRULUI TEXTUL FIRULUI Thread light (“proces usor”) Un fir de executie peste o zona mica continua de memorie care este primit de la (si din) procesul parinte
Exemplu Thread in Linux void *func ( ) { /* define local data */ - - - - - - - - - - - - - - - - - - - - - - /* function code */ thr_exit(exit_value); } main ( ) thread_t tid; int exit_value; thread_create (0, 0, func (), NULL, &tid); thread_join (tid, 0, &exit_value);
Paralelism in procese Date a b r1 c d r2 int add (int a, int b, int & result) // corpul int sub(int a, int b, int & result) Date Procesor a b r1 c d r2 IS1 add pthread t1, t2; pthread-create(&t1, add, a,b, & r1); pthread-create(&t2, sub, c,d, & r2); pthread-par (2, t1, t2); Processor IS2 sub
Paralelism date do “ dn/2 dn2/+1 dn sort( int *array, int count) //...... Procesor do “ dn/2 dn2/+1 dn Sortare pthread-t, thread1, thread2; “ pthread-create(& thread1, sort, array, N/2); pthread-create(& thread2, sort, array, N/2); pthread-par(2, thread1, thread2); IS Procesor Sortare
De ce thread-uri? Proces cu un singur fir de executie: apeluri blocante, executie secventiala Cu automat finit (bazat pe eveniment) apeluri neblocante si paralelism
Sistem de operare
Thread-uri utilizator 12/1/2017 Thread-uri utilizator Thread-urile sunt gestionate de o biblioteca de thread-uri
Thread-uri la nivel kernel 12/1/2017 Kernel-ul este constient de existenta thread-uri
Procese “usoare” (Light-weight-LWP) 12/1/2017 Procese “usoare” (Light-weight-LWP) Presupun maparea maparea mai multor LWP peste un proces real (greu - heavy-weight)
(UNIX, VMS, MVS, NT, OS/2 etc.) Sisteme Multitasking Proces Spatiu Utilizator Kernel Structura Procesului UNIX Hardware (UNIX, VMS, MVS, NT, OS/2 etc.)
Sisteme Multitasking Procese P1 P2 P3 P4 kernel Hardware Fiecare proces este independent kernel Hardware
Procese Multithread T1’s SP T3’sPC T1’sPC T2’sPC T1’s SP Cod Utilizator Date globale T2’s SP Structura Procesului Kernel
Maparea thread-urilor 1:1 DEC, NT, OS/1, AIX. IRIX M:1 HP-UNIX M:M 2-level
Modelul pe doua nivele al SunOS Proces Traditional Proc 1 Proc 2 Proc 3 Proc 4 Proc 5 Utilizator LWP-uri Thread-uri Kernel Kernel Hardware Procesoare
Paralelism la nivel de thread-uri
Multithreading – Mono procesor Concureta Vs Paralelism Concurenta P1 CPU P2 P3 timp
Multithreading - Multiprocesor Concurenta Vs Paralelism CPU P1 CPU P2 CPU P3 timp
Thread-uri la nivel user Model de lucru Thread-uri la nivel user Procesoare virtuale Procesoare fizice Planificare nivel user (User) Planificare nivel Kernel (Kernel)
Architectura generala a modelului bazat pe thread-uri Ascunde detaliile arhitecturii masinii Mapeaza thread-urile user peste cele native ale kernel Memoria procesului parinte este vazuta in comun de thread-uri
Modele de programare folosind thread-uri 1. master/slave Peer 3. pipeline
Modelul master slave Resurse Program sclavi Fisiere Baze Date Stapan taskX Baze Date Stapan taskY main ( ) Input (Stream) Disc-uri taskZ Dispozitive Speciale
Modelul peer Resurse Input (static) Program Sclavi Fisiere Baze Date 12/1/2017 Program Resurse Sclavi Input (static) Fisiere taskX Baze Date taskY Disc-uri taskZ Dispozitive Speciale
Pipeline de thread-uri Program Filtru Thread-uri Nivel 1 Nivel 2 Nivel 3 Input (Stream) Fisiere Baze date Disc-uri Dispozitive Speciale Fisiere Baze date Disc-uri Dispozitive Speciale Fisiere Baze date Disc-uri Dispozitive Speciale Resurse
Observatii privind utilizarea thread-urilor Daca toate operatiile sunt cu mai consumatoare de procesor nu este recomandat sa se lucreze pe thread-uri Desi crearea este simpla totusi foloseste ea insasi niste resurse Thread-urile prea simple (5 linii) nu sunt eficiente
Detalii fire de executie
Cai de creare a unui thread in Java class MyThread extends Thread { public void run() // corpul thread ce trebuie executat } MyThread thr1 = new MyThread(); thr1.start();
Se creaza o clasa care implementeaza interfata Runnable class ClassName implements Runnable { ..... public void run() // corpul thread ce trebuie executat } ClassName myObject = new ClassName(); Thread thr1 = new Thread( myObject ); thr1.start();
Exemplul 2 class ThreadDemo implements Runnable { ThreadDemo() {Thread ct = Thread.currentThread(); System.out.println("Current Thread: "+ct); Thread t = new Thread(this,"Demo Thread"); t.start(); try { Thread.sleep(3000); } catch(InterruptedException e) { System.out.println("Interrupted."); } System.out.println("Exiting mainthread."); }
public void run() { try { for(int i=5; i>0; i--) { System.out.println(" " + i); Thread.sleep(1000); } } catch(InterruptedException e) { System.out.println("Child interrupted."); } System.out.println("Exiting child thread."); public static void main(String args[]) new ThreadDemo();
Gestiunea thread-ului curent class CurrentThreadDemo { public static void main(String arg[]) { Thread ct = Thread.currentThread(); ct.setName( "My Thread" ); System.out.println("Current Thread : "+ct); try { for(int i=5; i>0; i--) { System.out.println(" " + i); Thread.sleep(1000); }
Current Thread : Thread[My Thread,5,main] catch(InterruptedException e) { System.out.println("Interrupted.") } Run: Current Thread : Thread[My Thread,5,main] 5 4 3 2 1
Ciclul de viata al unui thread
Runnable Thread-ul este lansat în execuţie prin apelul metodei start() din clasa Thread. Not Runnable - se poate ajunge în această stare dacă:
Dead În această stare se ajunge când firul şi-a terminat execuţia. Metoda isAlive() returnează: true, dacă firul de execuţie a fost pornit şi nu a fost oprit (este în starea Runnable sau Not Runnable) false, dacă firul de execuţie este fie în starea New Thread, fie în starea Dead.
Cazurile în care un fir de execuţie poate ajunge din starea Not Runnable în starea Runnable Dacă a fost apelată metoda sleep(), atunci firul execuţie ajunge în starea Runnable după scurgerea intervalului de timp specificat. Dacă a fost apelată metoda wait(), atunci un alt fir de execuţie trebuie să-l informeze dacă acea condiţie este îndeplinită sau nu (folosind metodele notify() şi notifyAll() din clasa Object).
Oprirea temporară a unui fir de execuţie public static void sleep(long millis) throws InterruptedException public static void sleep(long millis,int nanos) throws InterruptedException De exemplu: try { // se face o pauza de o secunda Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace();}
Terminarea unui fir de execuţie Pentru ca un fir de execuţie să se termine trebuie ca metoda run() să-şi termine execuţia. Există două metode pentru terminarea unui fir de execuţie: Metoda run() îşi termină execuţia în mod natural: dacă avem instrucţiuni cu timp de execuţie finit Daca avem bucle finite.
Fire de execuţie de tip „daemon” Un „daemon” reprezintă un fir de execuţie care se termină automat la terminarea aplicaţiei. De obicei firele „daemon” pun la dispoziţia celorlalte fire de execuţie anumite servicii.
Proceselor/firelor de executie Planificarea Proceselor/firelor de executie
Alternarea seventelor de calcul cu rafalele (“burst”) de instructiuni de intrare iesire
Planificatorul CPU Selecteaza dintre procesele active in memorie pe cele care sunt gata a fi executate si rezerva procesorul pentru unul din ele (secvential pur) Deciziile de planificare pot aparea atunci cand un proces: 1. Trece din starea de executie in starea de asteptare 2. Trece din starea de executie in starea de gata de executie 3. Trece din starea de asteptare in starea de gata de executie 4. Se termina
Distribuitorul/Selectorul de procese - Dispatcher Acest modul da controlul procesorului catre procesul selectat de catre planificatorul pe termen scurt si implica Schimbarea contextului Trecerea in mod utilizator Saltul catre locatia corespunzatoare din programul utilizatorului pentru a-l reporni Intarzierea selectorului – timpul necesar acestuia pentrua a opri un proces si a-l porni pe urmatorul
Criterii pentru realizarea planificarii Utilizarea procesor Max Incarcare - Throughput – Max Turnaround time – Min Timp de asteptare – Min Timp de raspuns – ) Min
Planificare de tip primul venit primul servit (FCFS) Proces Timp operatii i/o P1 24 P2 3 P3 3 Sa presupunem ca procesele ajung in urmatoarea ordine: P1 , P2 , P3 Atunci diagrama Gantt pentru planificare este: P1 P2 P3 24 27 30
FCFS Dca procesele ajung in ordinea P2 , P3 , P1 atunci vom avea urmatoarea diagrama Gatt a planificarii lor P1 P3 P2 6 3 30
Cea mai scurta sarcina este prima (SJF) Se asociaza fiecarui process lungimea urmatorului set de operatii de I/O care il va efectua (CPU burst). Aceste lungimi sunt folosite pentru a planifica procesul cu cel mai scurt timp Exista doua abordari: Nepreemptiva preemptiva
Exemplu de SJF nepreemptiv Proces Timp sosire Timp operatii I/O P1 0.0 7 P2 2.0 4 P3 4.0 1 P4 5.0 4 SJF (non-preemptiv) P1 P3 P2 7 3 16 P4 8 12
Exemplu de SJF preemptiv Proces Timp de aparitie Timp pentru i/o P1 0.0 7 P2 2.0 4 P3 4.0 1 P4 5.0 4 SJF (preemptiv) P1 P3 P2 4 2 11 P4 5 7 16
Planificare bazata pe prioritati Un numar al prioritatii este asociat la fiecare proces Procesorul va fi dat procesului cu prioritatea cea mai mare (adica cel mai mic numar). Si in acest caz procesul de planificare poate fi: Preemptiv nepreemptiv Problema Starvation Solutie Aging
Round Robin (RR) Fiecare proces primeste o mica unitate din timpul procesorului (cuanta de timp – time quantum), de obicei durata acesteia este intre 10 si 100 milliseconde. Daca exista n procese in aceasta coada si cuanta de timp este q, atunci fiecare proces va avea acces la 1/n din timpul procesorului in bucati de cel mult q unitati de timp o data. Rezulta ca nici un proces nu va astepta mai mult de (n-1)q unitati de timp. Performanta q este mare FIFO q este mica q trebuie sa fie mare tinand insa cont de comutarea contextului altfel supraincarcarea obtinuta va fi prea mare
Exemplu de RR cu cuanta de 20 Proces Timp pentru i/o P1 53 P2 17 P3 68 P4 24 Diagrama Gantt asociata va fi: P1 P2 P3 P4 20 37 57 77 97 117 121 134 154 162
Cuanta de timp si timpul de de comutare a contextului
Timpul de executie a unui proces variaza functie de cuanta de timp
Cozi multinivel Coada “gata de executie” este formata din doua cozi separate foreground – interactiva background – fundal (batch) Fiecare coada are propriul ei algoritm de planificare foreground – RR background – FCFS Planificarea trebuie realizata intre cozi Planificare cu prioritate fixa Cu cuante de timp
Planificarea cozilor multinivel
Coada de raspuns (feedback) multinivel Un proces poate fi mutat ntre cozi diferite (de exemplu “imbatranirea” poate fi implenentata in aceasta maniera Un planificator pentru coada de raspuns multinivel (multilevel-feedback-queue) este definit de utmatorii parametri Numarul cozilor Algoritmi de planificare pentru fiecare coada Metodele folosite pentru a determina cand se creste prioritatea unui proces Metodele folosite pentru a determina cand se scade prioritatea unui proces Metodele folosite pentru a determina in care coada va intra un proces care trebuie tratat
Exemplu de coada de raspuns multinivel Fie trei cozi: Q0 – cu cuanta de timp 8 milisecunde Q1 – cu cuanta de timp de 16 milisecunde Q2 – FCFS
Planificarea in Linux Se folosesc doi algoritmi Time-sharing Real-time Bazat pe prioritati de credit (Prioritized credit-based) – Creditul este scazut atunci cand apare o intrerupere de timp Cand creditul = 0, se alege alt proces Cand toate procesele au creditul = 0, se reincepe procesul de creditare Real-time Soft real-time Daca este conform standardului Posix.1b vom avea doua categorii FCFS si RR Procesul cu cea mai mare prioritate este primul executat
Planificarea thread-urilor Locala atunci cand ninlioteca de thread-uri va decide Globala atunci cand decide kernel-ul
Planificarea thread-urilor in Java JVM foloseste o politica de planificarea preemptiva bazata pe prioritati JVM va planifica un thread spre executare atunci cand: Thread-ul care este in executie curenta va parasi starea Runnable Un thread cu o prioritate mai mare intra in starea Runnable
Time-Slicing Deoarece planificarea pe cuante nu este asigurata de JVM trebuie folosita metoda yield() while (true) { // fa un calcul intensiv computational . . . Thread.yield(); }
Prioritatile thread-urilor Prioritate tip Thread.MIN_PRIORITY prioritate minima Thread.MAX_PRIORITY prioritate maxima Thread.NORM_PRIORITY prioritate implicita Prioritatile pot fi schimbate folosind metoda setPriority() De ex setPriority(Thread.NORM_PRIORITY + 2);
Exemplu Prioritati thread class Clicker implements Runnable { int click = 0; private Thread t; private boolean running = true; public Clicker(int p) { t = new Thread(this); t.setPriority(p); } public void run() { while(running) click++; } public void start() { t.start(); } public void stop() { running = false; }
Exemplu Prioritati thread class HiLoPri { public static void main(String args[]) {Thread.currentThread().setPriority(Thread.MAX_PRIORITY); Clicker Hi = new Clicker(Thread.NORM_PRIORITY+2); Clicker Lo = new Clicker(Thread.NORM_PRIORITY-2); Lo.start(); Hi.start(); try Thread.sleep(10000); } catch (Exception e) { }
Lo. stop(); Hi. stop(); System. out. println(Lo. click + " vs. " + Hi Lo.stop(); Hi.stop(); System.out.println(Lo.click + " vs. " + Hi.click); } Run1: (on Solaris) 0 vs. 956228 Run2: (Window 95) 304300 vs. 4066666
Problema inversiunii prioritatilor in cazul sincronizarii Fie un proces L de prioritate scazuta si un proces H de prioritate crescuta. Ambele au acces la o resursa comuna unica (adica se permite numai acces mutual exclusiv)
Solutia pentru inversia prioritatii Protocol de mostenire a prioritatilor Daca thread-ul t1 incearca sa achizitioneze un lock care este detinut de un thread de prioritate scazuta t2, atunci se va creste temporar prioritatea lui t2 la nivelul celei detinute de t1 in timpul in care t2 mentine lock-ul Protocol cu simularea limitarii prioritatii (Highest locker) Unui monitor I se da o prioritate atunci cand este creat. Aceasta este cea mai mare prioritate pe care un thread care incearca sa intre in zona monitorizata o poate avea O data ce thread-ul intra in zona sincronizata prioritatea lui este crescuta la cea a monitorului.
Coerenta datelor Ce facem daca mai multi vor sa scrie simultan aceasi valoare? Cea mai simpla solutie:excluziunea mutuala
Variabile pentru sincronizare in memoria comuna Proces 1 Proces 2 Variabila pentru sinchronizare Memorie Comuna S S S S Thread
Lock Definitie O entitate care poate fi detinuta de un singur thread la un moment dat Proprietati Reprezinta o forma/tip de sincronizare Este folosita pentru a intari/realiza excluziunea mutuala Thread-urile pot achizitiona/elibera un lock
Obiecte sincronizate in Java TOATE obiectele java furnizeaza (pt ca detin) un lock Se aplica cuvantul cheie synchronized asupra obiect Se obtine excluziunea mutuala pentru secventa din bloc Exemplu Object x = new Object(); void foo() { synchronized(x) { ... } bloc
Metode sincronizate in Java Si metodele Java furnizeaza lock-uri Se aplica cuvantul cheie synchronized la metoda Se sincronizeaza pe un obiect (implicit) apeland metoda Example synchronized void foo() { …cod… } // este echivalenta cu void foo() { synchronized (this) { …cod… } } bloc
Sincronizarea accesului la campuri “mutable” 12/1/2017 Sincronizarea accesului la campuri “mutable” Acestea sunt de fapt camputile care permit modificarea continutului. Intr-un mediu parallel ( multi thread) accesarea unei variabile commune necesita coordonare explicita intre cei care scri si cei care citesc datorita problemei coerentei datelor. Procesul de coordonare in acest caz se numeste sincronizare.
12/1/2017 Exemplu import java.util.Date; public final class MutablePlanet { public MutablePlanet(int aId, String aName, Date aDateOfDiscovery) fId = aId; fName = aName; fDateOfDiscovery = new Date(aDateOfDiscovery.getTime()); }
12/1/2017 public synchronized int getId() {return fId;} public synchronized void setId(int aNewId) { fId = aNewId; } public synchronized String getName() { return fName; } public synchronized void setName(String aNewName) { fName = aNewName; } public synchronized Date getDateOfDiscovery() { return new Date(fDateOfDiscovery.getTime()); } public synchronized void setDateOfDiscovery( Date aNewDiscoveryDate) { fDateOfDiscovery.setTime(aNewDiscoveryDate.getTime()); }
12/1/2017 private int fId; private String fName; private Date fDateOfDiscovery; // }
Indicatii pentru crearea unei clase imutabila 12/1/2017 Indicatii pentru crearea unei clase imutabila Asigurati ca clasa nu poate fi suprascrisa folosind final sau fabric static impreuna cu constructori private Campurile clasei trebuie sa fie private si final Nu furnizati nici o metoda care poate schimba starea unui obiect in nici un fel (nu nu mai setere si getere nimic)
Exemplu import java.util.Date; public final class Planet { 12/1/2017 Exemplu import java.util.Date; public final class Planet { public Planet (double aMass, String aName, Date aDateOfDiscovery) fMass = aMass; fName = aName; fDateOfDiscovery = new Date(aDateOfDiscovery.getTime()); } public double getMass() { return fMass;
12/1/2017 public String getName() { return fName; } // public Date getDateOfDiscovery() { //not ok // return fDateOfDiscovery; // } public Date getDateOfDiscovery() {//ok return new Date(fDateOfDiscovery.getTime()); private final double fMass; private final String fName; private final Date fDateOfDiscovery;
12/1/2017 Lock In cazul lock-ului intrinsec operatiunea de lock va fi efectuata automat de catre Java (in spatele scenei). Utilizarea synchronized este asociata cu doua tipuri de lock intrinsec Un “lock pe instanta” care este atasat unui singur obiect Un “lock static” atasata unei clase
12/1/2017 In mod similar preluare controlului unui lock static va impedica celelalte thread-uri sa apeleze o metoda sincronizata static DANU NU va bloca apelul metodelor nesincronizate sau a instantelor de metoda sincronizate Lock-ul static poate fi obtinut in afara header-ului metodei in doua moduri synchronized(Blah.class),folosind clasa literal synchronized(this.getClass()), daca un obiect este disponibil
Exista doua categorii de operatii asupra unui obiect: Cu apel unic: Cu apel multiplu Exista mai multe situatii care trebuie analizate pentru a decide utilizarea lock-ului lock-never apelantul nu are nevoie nici o data sa obtina un lock extern inainte de a efectua orice tip de operatie.
lock-always – apelantul are nevoie intotdeauna sa obtina un lock extern inainte de efectuarea oricarei operatii. lock-sometimes – apelantul are nevoie cate o data sa obtina un lock extern inainte de a fectua unele operatii. Instantele sunt mutable iar implementarea clasei efectuare intern majoritatea sincronizarilor. lock-hostile – operatiile nu pot fi effectuate correct (safe) intr-un mediu multi-thread chiar daca se obtine tot tipul lock-ul extern.
Sincronizarea la nivel de sectiuni Controlul executiei
Hazard de curse - Data Race x = y = 0 Thread 1 Thread-urile pornesc Thread 2 x = 1 y = 1 j = y i = x Se poate sa avem ca rezultat i = 0 and j = 0?
Raspuns: DA! x = y = 0 Thread 1 Thread 2 Thread-urile pornesc x = 1 j = y i = x
Cum se poate intampla asa ceva? Compilatorul poate reordona instructiunile Sau sa mentina variabilele in registri Procesorul le poate reordona Modelul de memorie folosit este proiectat sa suporte otimizari agresive (cu relaxarea conditiilor) Poate include optimizari neimplementate in general
Sincronizarea Se foloseste cand Trebuie delimitat un bloc care trebuie sa aiba executie atomica (neintrerupta) de alt thread Introduce un punct de sincronizare a informatiei/comunicatiei intre thread-uri Obs Implica totusi o supraincarcare (mica) in timpul executiei.
Exemplu de operatiune neatomica public class OperNeAtomica implements Runnable { static int x = 0; public void run() { int tmp = x; x = tmp+1; } public static void main(String[] args) for (int i = 0; i < 3; i++) new Thread(new OperNeAtomica ()).start(); System.out.println(x); }
Probleme la sincronizare Utilizarea acelasi lock pentru a furniza excluderea mutuala Asigurarea tranzactiilor atomice Evitarea deadlock=ului
Utilizarea acelasi lock Lock comun Lock separat Exemplu void run() { Object o = new Object(); // diferit pentru fiecare thread synchronized(o) { … // sunt sanse sa apara data race }
Tranzactie atomica Problema potentiala Exemplu synchronized(lock) { int tmp = x; x = tmp; }
Utillizarea sincronizarii public class OperNeAtomica implements Runnable { static int x = 0; static Object lock = new Object(); public void run() { int tmp; synchronized(lock) { tmp = x; }; synchronized(lock) { x = tmp+1; } }
Evitarea blocajului reciproc In general este bine a se evita executia unor operatii consumatoare de timp intr-o zona de lock (cand se mentine controlul acestuia) Ce poate dura? Diverse… Aparitia altui lock Poate aparea blocaj reciproc (deadlock)
Exemplu Deadlock Thread1() { synchronized(a) synchronized(b) … ceva }
Exemplul 2 de deadlock void moveMoney(Account a, Account b, int amount) {synchronized(a) {synchronized(b) { a.debit(amount); b.credit(amount); } Thread1() { moveMoney(a,b,10); } Thread2() { moveMoney(b,a,100); }
Abstract Data Type – Buffer
Implementare Buffer public class Buffer { private LinkedList objects = new LinkedList(); public synchronized add( Object x ) { objects.add(x); } public synchronized Object remove() { while (objects.isEmpty()) { ; return objects.removeFirst();
Eliminare Deadlock public class Buffer { private Object [] myObjects; private int numberObjects = 0; public synchronized add( Object x ) { objects.add(x); } public Object remove() { while (true) { synchronize(this) { if (!objects.isEmpty()) { return objects.removeFirst(); } }
Metodele Wait & Notify wait() Apelata de un obiect notifyAll()
Utilizarea Wait si NotifyAll public class Buffer { private LinkedList objects = new LinkedList(); public synchronized add( Object x ) { objects.add(x); this.notifyAll(); } public synchronized Object remove() { while (objects.isEmpty()) { this.wait(); } return objects.removeFirst();
Codul devine public class Buffer { private LinkedList objects = new LinkedList(); public synchronized add( Object x ) {objects.add(x); this.notifyAll(); } public synchronized Object remove() { while (objects.isEmpty()) { try { this.wait(); } catch (InterruptedException e) {} } return objects.removeFirst();
Ce este un thread pool O colectie de thread-uri care sunt create simultan ( de exemplu cand un server porneste) NU este necesar ca serverul sa reeze cate un thread pe masura ce cererile de la clienti apar IN loc serverul poate folosi un thread care a fost deja creat si care fie este liber fie se va elibera in curand
De ce avem nevoie de thread pools Imbunatatesc utilizarea resurselor atentie crearea unui nou thread induce totusi o suprancarcare care nu poate neglijata Folosirea lor permite aplicatiilor sa isi controleze complet utilizarea thread-urilor interne
Utilizarea in servere Thread pools sunt importante in psecial pentru aplicatiile de tip client server Deoarece procesarea fiecarui task individual dureaza putin iasr numarul de cereri este mare Serverele nu trebuie sa consume mai mult timp si resurse pentru crearea si distrugerea thread-urilor decat pentru deservirea clientilor
Implementarea evidenta 12/1/2017 Implementarea evidenta Fie un pool de thread Fiecare task cere la pornire un thread si il returneaza pool-ului dupa ce si-a terminat treaba Care este problema aici Modelul “Synchronized” - clientul asteapta pana cand serverul ii satisface cererile
Implementarea evidenta …. nu e cea mai buna Cand pool-ul este gol threadu-l emitent de job trebuie sa astepte ca un thread sa fie disponibil De obicei se doreste evitarea blocarii acestui thread Un server ar putea dori sa efectueze unele actiuni cand vin prea multe cereri
Toate thread-urile worker asteapta pentru a primi treaba O solutie posibila Fiecare thread se uita dupa cereri (tasks) in coada wait() Coada Task-uri Thread-uri worker Daca Q este gol Toate thread-urile worker asteapta pentru a primi treaba
O solutie posibila Task Coada Task-uri Thread-uri worker Model “A-synchronized” “Lanseaza si uita ” Numarul de thread-uri worker este Fix. Atunci cand un task este introdus in coada Este apelat un notify
O solutie posibila Task Coada Task-uri Thread-uri Worker notify() Task Coada Task-uri Thread-uri Worker Numarul de thread-uri este fix Atunci cand un task este inserat In coada va fi apelat notify
Thread Pool Implementation public class TaskManager { LinkedList taskQueue = new LinkedList(); List threads = new LinkedList(); public TaskManager(int numThreads) {for(int i=0; i<numThreads; ++i) {Thread worker = new Worker(taskQueue); threads.add(worker); worker.start(); } public void execute(Runnable task) {synchronized(taskQueue) { taskQueue.addLast(task); taskQueue.notify(); }
Thread Pool Implementation public class Worker extends Thread { LinkedList taskQueue = null; public Worker(LinkedList queue) { taskQueue = queue; } public void run() { Runnable task = null; while (true) { synchronized (taskQueue) { while (taskQueue.isEmpty()) { try {taskQueue.wait();} catch (InterruptedException ignored) {} } task = (Runnable) taskQueue.removeFirst(); task.run();
Riscuri in folosirea Thread Pools Thread-urile pot avea scapari Sa se blocheze astepand terminarea unei operatii de i/o De exemplu clientul poate oprin interactiunea cu un socket fara a-l inchide corespunzator
Dimensiunea pool-ului Fiecare thread consuma resurse: memore, efort pentru management etc. Un pool mare poate conduce la infometare Task-urile care vin asteapta un thread liber
Tratarea a unui numar prea mare de cereri la un server multithtread Nu se adauga la coada de procesare toate cererile (se ignora sau se trimite un mesaj de eroare) Se folosesc cateva dimensiuni predefinite pentru pool conform tipului de incarcare al serverului ( dar ete bine ca sa nu schimbam prea des dimensiunea pool-ului)
Calculul dimensiunii pool Telul principale este: ca procesarea sa continue chiar daca se astepata ca operatii lente de I/O sa se termine Fie urmatoarele variabile: WT = timpul mediu estimat ST= timpul mediu de rezolvare estimat pentru o cerere (fara timpii de asteptare) Atunci: WT/ST+1 thread-uri vor fi de ajuns ca sa mentina procesorul la incarcare maxima
12/1/2017 Clasa Executor Are doua metode statice pentru crearea de pool-uri de thread-uri ExecutorService newFixedThreadPool(int nThreads) Poate crea un pool de dimensiune fixa ExecutorService newCachedThreadPool() Creaza noi thread-uri functie de necesitati Noile thread-uri sunt adaugate la pool si reciclate ExecutorService has an execute method void execute(Runnable command)
{ private final ServerSocket serverSocket; class NetworkService { private final ServerSocket serverSocket; private final ExecutorService pool; public NetworkService(int port, int poolSize) throws IOException { serverSocket = new ServerSocket(port); pool = Executors.newFixedThreadPool(poolSize); } public void serve() { try for (;;) { pool.execute(new Handler(serverSocket.accept()));} } catch (IOException ex) { pool.shutdown();
{ private final Socket socket; Handler(Socket socket) class Handler implements Runnable { private final Socket socket; Handler(Socket socket) { this.socket = socket; } public void run() { // citeste si deserveste cererea} }
Referinte https://www.cs.umd.edu/class/spring2006/cmsc132/ http://people.cs.uchicago.edu/~asiegel/courses/cspp51037/ http://www.cis.upenn.edu/~lee/01cis642/ http://users.ece.gatech.edu/~copeland/jac/3055-05/ http://www.buyya.com/ http://lass.cs.umass.edu/~shenoy/courses/spring05/ http://www.jguru.com/faq/view.jsp?EID=143462 http://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html http://docs.oracle.com/javase/tutorial/essential/concurrency/exinter.html http://java2all.com/technology/core-java/multithreading/ thread-life-cycle
After …. this course