Transcript P 1
Curs 8
Procesare paralela in Java
1
Arhitecturi Paralele
Clasificare arhitecturi paralele
• Clasificarea Flyn
–SISD - conventional
–SIMD - calcul vectorial
–MISD - scalcul sistolic
–MIMD – cazul general
SISD : Von Neuman
Instructiuni
Date Intrare
Procesor
Date Iesire
Flux de
Instructiuni A
Arhitectura MISD
Flux de
Instructiuni B
Flux de
Instructiuni C
Procesor
A
Flux de
date
de Intrare
Flux de
date
de Iesire
Procesor
B
Procesor
C
Architectura SIMD
Flux de
Instructiuni
Flux de
Date de
Intrare A
Flux de
Date de
Intrare B
Flux de
Date de
Intrare C
Flux de
Date de
Iesire A
Procesor
A
Flux de
Date de
Iesire B
Procesor
B
Procesor
C
Ci<= Ai * Bi
Flux de
Date de
Iesire C
Architectura MIMD
Flux de
Flux de
Flux de
Instructiuni A Instructiuni B Instructiuni C
Flux de
Date de
Intrare A
Flux de
Date de
Intrare B
Flux de
Date de
Intrare C
Flux de
Date de
Iesire A
Procesor
A
Flux de
Date de
Iesire B
Procesor
B
Procesor
C
Flux de
Date de
Iesire C
Masina MIMD cu memorie comuna
Procesor
A
M
E
M
O
R
I
E
M
A
G
I
S
T
R
A
L
A
Procesor
B
M
E
M
O
R
I
E
M
A
G
I
S
T
R
A
L
A
Procesor
C
M
E
M
O
R
I
E
M
A
G
I
S
T
R
A
L
A
Sistem de Memorie Globala
MIMD cu memorie distribuita
Canal
Canal
Comunicatie
Comunicatie
Procesor
A
M
E
M
O
R
I
E
M
A
G
I
S
T
R
A
L
A
Memorie
Sistem A
Procesor
B
M
E
M
O
R
I
E
M
A
G
I
S
T
R
A
L
A
Memorie
Sistem B
Procesor
C
M
E
M
O
R
I
E
M
A
G
IS
T
R
A
L
A
Memorie
Sistem C
Nivele de paralelism
Nivele de Paralelism
Task i-l
Task i
func1 ( )
{
....
....
}
a ( 0 ) =..
b ( 0 ) =..
+
func2 ( )
{
....
....
}
a ( 1 )=..
b ( 1 )=..
x
Task i+1
func3 ( )
{
....
....
}
a ( 2 )=..
b ( 2 )=..
Load
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
Paralelism la nivel de
procese
Procesul clasic
Stiva
Date
Stiva
Memorie
COMUNA,
segmente,
pipes,
Fisiere
deschise sau
mapate in
memorie
Text
procese
Date
Text
Memorie comuna
Mentinuta de kernel
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
Fire de executie
Thread-uri
Flux unic de instructiuni
Spatiu de adrese
COMUN pt fire
Al procesului
Proces cu mai multe fire de
executie
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)
•
Context
Hardware/
software
Registri
Cuvant stare
Program Counter
executare
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
18
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,
- - - - - - - - thread_join (tid,
- - - - - - - - }
- 0, func (), NULL, &tid);
- 0, &exit_value);
- -
Paralelism in procese
int add (int a, int b, int & result)
// corpul
int sub(int a, int b, int & result)
// corpul
Date
Procesor
IS1
pthread t1, t2;
pthread-create(&t1, add, a,b, & r1);
pthread-create(&t2, sub, c,d, & r2);
pthread-par (2, t1, t2);
add
Processor
IS2
sub
a
b
r1
c
d
r2
Paralelism date
sort( int *array, int count)
//......
//......
pthread-t, thread1, thread2;
“
IS
“
pthread-create(& thread1, sort, array, N/2);
pthread-create(& thread2, sort, array, N/2);
pthread-par(2, thread1, thread2);
Date
Procesor
Sortare
Procesor
Sortare
do
“
“
dn/2
dn2/+1
“
“
dn
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
• Thread-urile sunt gestionate de o biblioteca de
thread-uri
Thread-uri la nivel kernel
• Kernel-ul este constient de existenta thread-uri
Procese “usoare” (Light-weight-LWP)
• Presupun maparea maparea mai multor LWP
peste un proces real (greu - heavy-weight)
Sisteme Multitasking
Proces
Spatiu
Utilizator
Structura Procesului
Spatiu
Kernel
UNIX
Hardware
(UNIX, VMS, MVS, NT, OS/2 etc.)
Sisteme Multitasking
Procese
P1
P2
kernel
Hardware
P3
P4
Procese Multithread
T1’s SP T3’sPC T1’sPC T2’sPC
T1’s SP
Cod
Utilizator
T2’s SP
Date globale
Structura Procesului
Kernel
Maparea thread-urilor
M:1
HP-UNIX
1:1
DEC, NT, OS/1, AIX. IRIX
2-level
•
M:M
Modelul pe doua nivele al SunOS
Proces
Traditional
Proc 1 Proc 2
Proc 3
Utilizator
LWP-uri
Thread-uri
Kernel
Kernel
Hardware
Procesoare
Proc 4
Proc 5
Paralelism la nivel de
thread-uri
Multithreading – Mono procesor
• Concureta Vs Paralelism
Concurenta
P1
P2
CPU
P3
timp
Multithreading - Multiprocesor
Concurenta Vs Paralelism
CPU
P1
CPU
P2
CPU
P3
timp
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
2. Peer
3. pipeline
Modelul master slave
Program
sclavi
taskX
Resurse
Fisiere
Baze Date
Stapan
taskY
Input (Stream)
main ( )
Disc-uri
taskZ
Dispozitive
Speciale
Modelul peer
Program
Sclavi
taskX
Resurse
Fisiere
Input
(static)
Baze Date
taskY
Disc-uri
taskZ
Dispozitive
Speciale
Pipeline de thread-uri
Program
Filtru Thread-uri
Nivel 1
Nivel 2
Nivel 3
Input (Stream)
Resurse
Fisiere
Fisiere
Fisiere
Baze date
Baze date
Baze date
Disc-uri
Dispozitive
Speciale
Disc-uri
Dispozitive
Speciale
Disc-uri
Dispozitive
Speciale
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);
}
}
catch(InterruptedException e)
{
System.out.println("Interrupted.")
}
}
}
Run:
Current Thread : Thread[My Thread,5,main]
5
4
3
2
1
48
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ă:
–
50
• 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.
51
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).
52
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();}
53
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.
54
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.
•
55
Planificarea
Proceselor/firelor de
executie
56
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
•
0
P2
24
P3
27
30
FCFS
Dca procesele ajung in ordinea P2 , P3 , P1 atunci
vom avea urmatoarea diagrama Gatt a
planificarii lor
P2
0
•
P3
3
P1
6
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
0
•
3
P3
7 8
P2
P4
12
16
Exemplu de SJF preemptiv
Proces Timp de aparitie
P1
0.0
P2
2.0
P3
4.0
P4
5.0
• SJF (preemptiv)
P1
0
•
P2 P3
2
4
P2
5
Timp pentru i/o
7
4
1
4
P4
7
P1
11
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 P1 P3 P4 P1 P3 P3
0 20 37 57 77 97 117 121134 154162
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
– 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:
1. Thread-ul care este in executie curenta va parasi
starea Runnable
2. 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
Thread.MIN_PRIORITY
Thread.MAX_PRIORITY
Thread.NORM_PRIORITY
tip
prioritate minima
prioritate maxima
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.click);
}
}
Run1: (on Solaris)
0 vs. 956228
Run2: (Window 95)
304300 vs. 4066666
82
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
Variabila pentru
sinchronizare
Proces 1
S
Proces 2
Memorie Comuna
S
Thread
S
S
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
bloc
Object x = new Object();
void foo() {
synchronized(x)
{
...
}
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
bloc
synchronized void foo() { …cod… }
// este echivalenta cu
void foo() {
synchronized (this) { …cod… }
}
• 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.
90
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());
}
91
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());
}
92
private int fId;
private String fName;
private Date fDateOfDiscovery; //
}
93
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)
94
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;
}
95
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;
}
96
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
•
97
• 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 headerului metodei in doua moduri
– synchronized(Blah.class),folosind clasa literal
– synchronized(this.getClass()), daca un obiect este
disponibil
98
• 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.
99
• 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.
100
Sincronizarea
la nivel de sectiuni
Controlul executiei
Hazard de curse - Data Race
x = y = 0
Thread 1
Thread-urile pornesc
x = 1
y = 1
j = y
i = x
Thread 2
Se poate sa avem ca rezultat i = 0 and j = 0?
Raspuns: DA!
x = y = 0
Thread 1
Thread-urile pornesc
x = 1
y = 1
j = y
i = x
Thread 2
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
}
}
}
Thread2()
{
synchronized(b)
{
synchronized(a)
{
… 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()
– Apelata de un obiect
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
1. O colectie de thread-uri care sunt create
simultan ( de exemplu cand un server
porneste)
2. NU este necesar ca serverul sa reeze cate
un thread pe masura ce cererile de la
clienti apar
3. 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
• 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
O solutie posibila
Fiecare thread se uita
dupa cereri (tasks) in
coada
wait()
Coada Task-uri
Daca Q este gol
Thread-uri worker
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
notify()
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
130
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
– Un pool mare poate conduce la infometare
Tratarea a unui numar prea mare de
cereri la un server multithtread
1. Nu se adauga la coada de procesare
toate cererile (se ignora sau se trimite
un mesaj de eroare)
2. 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)
132
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
133
Clasa Executor
• Are doua metode statice pentru crearea de pooluri 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)
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();
}
}
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/jls17.html
http://docs.oracle.com/javase/tutorial/essential/concurrenc
y/exinter.html
http://java2all.com/technology/core-java/multithreading/
thread-life-cycle
137
After …. this course
138