Documente Academic
Documente Profesional
Documente Cultură
paralelismului de date
Ciprian Dobre
ciprian.dobre@cs.pub.ro
Programarea concurentă în Java
Premise
Un spațiu în memorie
(codul programului, zona Code Code
de date, stiva)
Controlul anumitor resurse Data Data
(fișiere, dispozitive I/O, …)
Stack Stack
Fire de execuție
Process 1 Process 2
Process
Control Block
Process Process
Control Block Control Block
Code Data
P1 Thread 1
Sisteme multiprocesor:
P2 Thread 2
→ execuție în paralel
t
Sisteme uniprocesor:
→ planificarea este
realizată de către sistemul
t de operare
Class MyThread extends Thread { 1 Class MyModule extends Module implements Runnable {
1
2 public void run() {
2 public void run() {
3 ...
3 ... Thread t = Thread.currentThread();
4
4 sleep(100); 5 t.sleep(100);
5 ... 6 ...
6 } 7 }
} 8 }
7
9 ...
8 ...
10 MyModule mm = new MyModule();
9 MyThread mt = new MyThread();
11 Thread mmt = new Thread(mm);
10 ... 12 ...
11 mt.start(); 13 mmt.start();
Controlul execuției thread-urilor
• De către JVM:
Prin intermediul priorităților: între Thread.MIN_PRIORITY (1)
și Thread.MAX_PRIORITY(10)
Scenariu:
• instanțiem un obiect x de
1 class TestMem { tipul TestMem
2 private int x = 1;
3 private long y = 2; • pornim două fire de
4
5 public void modifică() {
execuție
6 x = 10;
7 y = 20;
• într-un fir de execuție
8 } apelăm x.modifică(),
9 iar în celălalt apelăm
10 public void afișează() {
System.out.println("x= " + x);
x.afișează()
11
12 System.out.println("y= " + y);
13 }
Întrebare:
14 }
Putem ști exact ce se va afișa
pe ecran?
Exemplu
1
2
class TestSincronizare extends Thread {
public static void main (String args[]) {
Rezultat
3 FirNeSincronizat f1 = new FirNeSincronizat("1"); GATA main!
4 FirNeSincronizat f2 = new FirNeSincronizat("2"); 2 a = 0 b = 0
5 1 a = 0 b = 0
1 a = 2 b = 1
6 f1.start(); 2 a = 3 b = 2
7 f2.start(); 1 a = 4 b = 3
8 2 a = 5 b = 4
GATA!
9 System.out.println("GATA main!"); GATA!
10 }
11 }
Sincronizarea firelor de execuție
• Două situații:
Concurență
Cooperare
• Sincronizarea
asigură excluderea mutuală – un singur fir poate executa la un
moment dat o metodă (secvență de cod) sincronizată: secțiune
critică
Folosește mecanismul de zăvor :
Fiecare obiect are asociat câte un zăvor
synchronized(o) asigură intrarea în secțiunea critică
se poate asocia unei metode sau unei secvențe de cod
pe parcursul execuției secvențelor de cod sincronizate, zăvorul este
„închis”
Exemplu: Sincronizare pentru accesul concurent la
o resursă (1)
Class Singleton {
private static Singleton uniqueInstance = null;
private Singleton( ) { .. } // private constructor
public static Singleton getInstance( ) {
if (uniqueInstance == null)
uniqueInstance = new Singleton();
// call constructor
return uniqueInstance;
}
}
20
Instrumente pentru dezvoltarea programelor – Curs 7
Singleton: Double check locking
21
Singleton: Double check locking
• sau….
Metoda wait()
1 class ZonăTampon {
2 private int valoare; // valoarea curentă din tampon
3 private boolean disponibil = false; //existența unei valori pentru Consum
4 public synchronized int aPreluat() {
5 if (!disponibil) {
6 try {
7 wait();
8 } catch (InterruptedException e) {}
9 }
10 disponibil = false;
11 notify();
12 return valoare;
13 }
14 public synchronized void aTransmis(int valoare) {
15 if (disponibil) {
16 try {
17 wait();
18 } catch (InterruptedException e) {}
19 }
20 this.valoare = valoare;
21 disponibil = true;
22 notify();
23 }
24 }
Suportul pentru concurență în JDK 5.0
• Semaphore
• Mutex
• CyclicBarrier
barieră reutilizabilă
are ca argument un contor care arată numărul de fire
din grup
• CountDownLatch
similar cu bariera, are un contor, dar decrementarea
contorului este separată de așteptarea ajungerii la
zero
decrementarea semnifică terminarea unor operații
• Exchanger
rendez-vous cu schimb de valori în ambele sensuri
între threaduri
Facilități pentru sincronizare de nivel scăzut
• Lock
generalizare lock monitor cu așteptări contorizate,
întreruptibile, teste etc.
• ReentrantLock
• Conditions
permit mai multe condiții per lock
• ReadWriteLock
exploatarea faptului că, la un moment dat, un singur
fir modifică datele comune și ceilalți doar citesc
• Variabile atomice:
AtomicInteger
AtomicLong
AtomicReference
permit execuția atomică read-modify-write
Exemplu: Utilizare semafoare in Java 1.5
• Embarrassingly parallel
• Aplicații:
Vectori
Matrice
Liste
Aplicații folosind paralelismul de date
Calculul sumelor prefix
Problemă:
Se dă tabloul a[1:n], se cere s[1:n], unde:
𝒔 𝒊 = σ𝒊𝒌=𝟏 𝒂[𝒌]
Algoritm secvențial:
s[1] = a[1];
for [i = 2 to n]
s[i] = a[i] + s[i-1];
Algoritm paralel:
- derivat din algoritmul sumei elementelor unui vector
Suma elementelor unui vector
7
0
3 7
0 4
1 3 5 7
0 2 4 6
0 1 2 3 4 5 6 7
𝑛
sup procesoare, sup log 2 𝑛 pași
2
Suma elementelor unui vector
a1 a2 a3 a4 a5 a6 a7 a8
Timp
a1 𝒔𝟐𝟏 a3 𝒔𝟒𝟏 a5 𝒔𝟔𝟓 a7 𝒔𝟖𝟓
int a[1:n];
process suma[k=1 to n] { // suma proceselor p din figura
for [j = 1 to sup(log 2 𝑛)] {
if (k mod 2j == 0)
a[k] = a[k-2j-1] + a[k];
barrier;
} p1 p2 p3 p4 p5 p6 p7 p8
}
a1 a2 a3 a4 a5 a6 a7 a8
Timp
a1 𝒔𝟐𝟏 a3 𝒔𝟒𝟏 a5 𝒔𝟔𝟓 a7 𝒔𝟖𝟓
a1 a2 a3 a4 a5 a6
Timp
𝒔𝟏𝟏 𝒔𝟐𝟏 𝒔𝟑𝟏 𝒔𝟒𝟏 𝒔𝟓𝟐 𝒔𝟔𝟑
int a[1:n];
process suma[k=1 to n] {
for [j = 1 to sup(log 2 𝑛)] {
if (k - 2j-1 >= 1)
a[k] = a[k-2j-1] + a[k];
barrier;
} a1 a2 a3 a4 a5 a6
}
a1 𝒔a𝟐𝟏2 a3 a4 a5 a6
𝒔𝟑𝟐 = 𝒂𝟑 + 𝒔𝟐𝟏
Sume prefix – varianta 2
𝒕𝟏 𝒕𝟐
𝒔𝟐𝟏 𝒔𝟑𝟐
Sume prefix – varianta 3
a1 a2 a3 a4 a5 a6
Pas 3: P1, P2, P3, P4 𝒔𝟏𝟏 𝒔𝟐𝟏 𝒔𝟑𝟏 𝒔𝟒𝟏 𝒔𝟓𝟏 𝒔𝟔𝟏
Sume prefix – varianta 2 SIMD
int A[1:N];
for [j = 0 to log N – 1] {
process Pk[k = 2j+1 to N] {
int temp;
/* temp locala procesorului k */
1. temp = A[k-2j];
2. A[k] = temp + A[k]
}
}
Operații cu vectori – broadcast
Pas 2:
for [i = 0 to (log N -1)] {
process Pj[j = 2i+1 to 2i+1]{ /*(Procesor Pj)*/
int t; /* t locală Pj */
t = A[j-2i];
A[j] = t;
}
}
Operații cu vectori – broadcast
1 2 3 4 5 6 7 8
15 15 15 15 15 15 15 15
Operații cu matrice - produs
cap 3 4 2 5 0
cap 3 4 2 5 0
Date Date Date Date Date
1 2 3 4 5