1213 threads [4] sincronització
TRANSCRIPT
PROCESSOS CONCURRENTS I MEMÒRIA COMPARTIDA
Els fils (threads) tenen un costat fosc : Poden donar l loc a problemes de concurrència.
Els problemes de concurrència donen lloc a “condicions de carrera“ (race condi*ons). Les “condicions de carrera” donen lloc a dades corruptes. Hem arribat a una situació fa1dica: Dos o més fils accedint a les dades d’un únic objecte. Quan un fil no està acMu o corrent és com si esMgués inconscient. Quan (de nou) és acMvat no sap que ha estat aturat.
PROCESSOS CONCURRENTS I MEMÒRIA COMPARTIDA Exemple 1: Molta llet
Hora Persona 1 Persona 2 15:00 Mirar en la nevera: No hi ha
llet!! 15:05 SorMr al carrer 15:10 Arribar a la boMga Mirar en la nevera: No hi ha llet!! 15:15 Comprar un tetra-‐brick de llet SorMr al carrer 15:20 Posar el brick de llet en la
nevera Arribar a la boMga
15:25 Comprar un tetra-‐brick de llet 15:30 Posar el brick de llet en la nevera.
Ep!! Ja n’hi ha!!
PROCESSOS CONCURRENTS I MEMÒRIA COMPARTIDA Exemple 1: Molta llet
Variable compartida Tasca a realitzar
PROCESSOS CONCURRENTS I MEMÒRIA COMPARTIDA Exemple 1: Molta llet
Iniciem els fils d’execució amb la tasca a realitzar. Comparteixen el mateix objecte nevera!!
PROCESSOS CONCURRENTS I MEMÒRIA COMPARTIDA
Exemple 2: Two people, one account
En Ricard s’adorm després d’haver comprovat el saldo del compte corrent (100€) però abans de confirmar l’execució d’un traspàs de 90€ a un altre compte corrent.
Quan en Ricard es desperta, immediatament finalitza l’execució del traspàs de diners
sense haver comprovar (un altre cop) el saldo del compte corrent. → El saldo del compte corrent passa a estar en números vermells!!
La TaMana -‐ parella de’n Ricard -‐ se’n va a un caixer per treure diners (90€). Consulta el saldo per confirmar que tot està correcte (100€). I treu els diners.
PROCESSOS CONCURRENTS I MEMÒRIA COMPARTIDA
Exemple 2: Two people, one account
Què ha passat? 2 fils (Ricard i Tatiana) comparteixen un mateix objecte (el compte corrent). El codi té dues classes, Comptecorrent, i TascaTatiRicard. La classe TascaTatiRicard implementa un Runnable i representa el comportament que tenen tant en Ricard com la TaMana – comprovar el saldo del compte i reMrar diners –. Lògicament, cada fil (thread) cau adormit entre les dues comprovacions del saldo i, de fet, quan es fa la reMrada de diners. La classe TascaTatiRicard té una variable de Mpus CompteCorrent que representa el seu propi compte corrent comparMt.
CompteCorrent
int saldo
mostraSaldo()
retirar() TascaTatiRicard
compteCorrent compte
run()
retirarDiners()
Runnable
PROCESSOS CONCURRENTS I MEMÒRIA COMPARTIDA
Exemple 2: Two people, one account – Codi resultant q Creem una instància de TascaTatiRicard.
La classe TascaTatiRicard és el Runnable (la feina a fer) I, donat que tant la TaM com en Ricard fan el mateix (comprovar el saldo I reMrar diners), necessitem realitzar una única instància.
tascaTatiRicard laTasca = new tascaTatiRicard ();
q Creem 2 fils amb el mateix Runnable (la instància TascaTatiRicard)
thread u = new Thread (laTasca); thread dos = new Thread (laTasca);
q Anomenem i arranquem els fils (threads) u.setName (”Ricard”); dos.setName (”Tatiana”) ; u.start( ); dos.start( );
PROCESSOS CONCURRENTS I MEMÒRIA COMPARTIDA
Exemple 2: Two people, one account – Codi resultant q Observem com els fils executen el mètode run ()
(comprovar el saldo I retirar diners)
Un fil representa en Ricard I l’altre representa la TaMana. Els dos fils estan congnuament comprovant el saldo i realitzen una reMrada de diners sempre I quan sigui possible.
if (CompteCorrent.mostrarSaldo ( ) ≥ quantitat) {
try { Thread.sleep(500); } catch (InterruptedException ex) {ex.printstackTrace ( ); }
}
PROCESSOS CONCURRENTS I MEMÒRIA COMPARTIDA
Exemple 2: Two people, one account – Codi resultant class compteCorrent {
private int saldo = 100; public int mostrarSaldo () {
return saldo; }
public void reMrar (int quanMtat) { saldo = saldo -‐ quanMtat; }
} public class tascaTaMRicard implements Runnable { private compteCorrent compte = new CompteCorrent(); public staMc void main (String [] args) {
tascaTaMRicard laTasca = new tascaTaMRicard (); Thread u = new Thread (laTasca); Thread dos = new Thread (laTasca); u.setName(“Ricard”); dos.setName(“TaMana”); u.start(); dos.start();
}
El compte comença amb un saldo de 100€.
Únicament hi haurà UNA instància de tascaTatiRicard. Això vol dir solament UNA instància del compte corrent. Ambdós fils accediran a aquest únic compte corrent.
Instanciem el Runnable (tasca)
Construïm dos fils; donem a cadascun d’ells la mateixa feina del Runnable. Ambdós fils accediran a la variable de instància de compte en la classe Runnable.
PROCESSOS CONCURRENTS I MEMÒRIA COMPARTIDA
Exemple 2: Two people, one account – Codi resultant public void run () {
for (int x = 0; x < 10; x++) { reMrarDiners (10) ;
if (compteCorrent.mostraSaldo() < 0) { System.out.println(“Números vermells");
} }
} private void reMrarDiners (int quanMtat) {
if (compte.mostrarSaldo() >= quanMtat) { System.out.println(Thread.currentThread() .getName() + “ està a punt de reMrar diners”);
try { System.out.println (Thread . currentThread() .getName() + “ s’adormirà”); Thread .sleep(500);
} catch(InterruptedExcepMon ex) {ex.printStackTrace(); } System.out.println (Thread. currentThread() .getName() + “ s’ha despertat”); compte.reMrar(quanMtat); System.out.println (Thread. currentThread() .getName() + “ Completa la reMrada”); } else { System.out.println(“Ho senMm,no hi ha fons per ” + Thread.currentThread().getName()); }
} }
En el mètode run (), un fil entra en un bucle i intenta fer una retirada amb cada iteració. Després de la retirada de diners, comprova el saldo un cop més per comprovar si el compte està en números vermells.
Comprovem el saldo del compte i, si no hi ha prou diners, mostrem un missatge. Si hi ha prou diners, ens anem a dormir; posteriorment, ens despertem i acabem la retirada (tal i com va fer en Ricard).
Imprimim per pantalla un grapat de declaracions per poder veure el que està succeint mentre s'executa.
PROCESSOS CONCURRENTS I MEMÒRIA COMPARTIDA
Exemple 2: Two people, one account – Codi resultant El mètode reMrarDiners() sempre comprova el saldo abans de realitzar la reMrada de diners; tot i això, el compte sempre es queda en números vermells. Un possible escenari: En Ricard comprova el saldo, mira que hi ha prou diners i, llavors, s’adorm. Mentrestant, la TaMana va i comprova el saldo. Ella, també, veu que hi ha prou diners. No té ni idea que en Ricard es despertarà I completarà la reMrada de diners. En Ricard es desperta i enllesteix la reMrada de diners. La TaM “es desperta” I completa la seva reMrada. Gran problema!! Entre el moment en que ella ha comprovat el saldo i que ha fet la reMrada, en Ricard s’ha despertat i ha reMrat diners del compte.. La comprovació del compte per part de la TaMana no és vàlida perquè en Ricard ja havia comprovat I estava enmig de la realització de la reMrada. L’accés al compte per part de la TaMana s’hagués hagut d’aturar fins que en Ricard hagués despertat I hagués finalitzat la seva transacció. I a l’inrevés, també.
PROCESSOS CONCURRENTS I MEMÒRIA COMPARTIDA
Exemple 2: Two people, one account – Codi resultant
Necessiten un cadenat per gestionar l’accés al
compte
Hi hauria un cadenat associat amb les transaccions dels comptes bancaris (comprovació del saldo i reMrada de diners). Solament hi hauria una clau que es manMndria amb el cadenat fins que algú volgués accedir al compte.
1
L a t r a n s a c c i ó d e l compte bancari està oberta quan ningú està utilitzant el compte.
El cadenat treballaria tal I com segueix:
PROCESSOS CONCURRENTS I MEMÒRIA COMPARTIDA
Exemple 2: Two people, one account – Codi resultant
Quan en Ricard volgués accedir al compte corrent del banc (per comprovar el saldo i realitzar una reMrada de diners), tancaria el cadenat I es guardaria la cau a la butxaca. En conseqüència, ningú més podria accedir al compte donat que la clau ja no estaria disponible.
2
Quan en Ricard vol accedir al compte, tanca el cadenat i es queda amb la clau.
PROCESSOS CONCURRENTS I MEMÒRIA COMPARTIDA
Exemple 2: Two people, one account – Codi resultant
En Ricard manKndria la clau a la butxaca fins que finalitzés la transacció. Com que ell Mndria l’única clau pel cadenat, la TaMana no podria accedir al compte (o a la comprovació) fins que en Ricard hagués deixat el cadenat, de nou, obert. Inclús si en Ricard caigués adormit després de comprovar el saldo, Kndria la garanKa de que el saldo seria el mateix quan es despertés perquè mentre hagués dormit, hauria Kngut la clau guardada en la seva butxaca.
3
Quan en Ricard acaba, o b r e e l c a d e n at i retorna la clau. Ara la clau està disponible per la Tati o per en Ricard per si, de nou, vol accedir al compte.
PROCESSOS CONCURRENTS I MEMÒRIA COMPARTIDA
Exemple 2: Two people, one account – Codi resultant
Necessitem que el mètode reMrarDiners () funcioni com quelcom atòmic.
Necessitem assegurar-‐nos de que, un cop que el fil entri en el mètode reMrarDiners (), pugui finalitzar el mètode abans de que qualsevol altre fil pugui entrar-‐hi. És a dir, que un cop que un fil hagi comprovat el saldo, aquest Mndrà la garanMa de que pot adormir-‐se i despertar més tard i finalitzar la reMrada de diners abans de que qualsevol altre fil pugui comprovar el saldo.
PROCESSOS CONCURRENTS I MEMÒRIA COMPARTIDA
Exemple 2: Two people, one account – Codi resultant
Necessitem que el mètode reMrarDiners () funcioni com quelcom atòmic.
En programació, una acció ATÒMICA és aquella que succeix amb efectivitat en/d’un sol vez. Una acció atòmica no es pot aturar a mig fer (o succeeix completament o no succeeix en absolut).
PROCESSOS CONCURRENTS I MEMÒRIA COMPARTIDA
Exemple 2: Two people, one account – Codi resultant
Si uMlitzem la paraula clau synchronized podrem modificar un mètode perquè tant sols un fil pugui accedir-‐hi. En el cas de l’exemple, serà així com protegirem el compte corrent del banc. No posarem un cadenat al compte del banc. Posarem el cadenat al que realitza la transacció bancària. Així, un fil podrà completar tota una transacció – de principi a fi – inclús si el fil s'adormís a meitat del mètode.
La paraula clau synchronized vol dir que un fil necessita una clau per accedir al codi sincronitzat. Per protegir les nostres d a d e s , h a u r e m d e sincronitzar els mètodes que actuïn sobre aquestes dades.
PROCESSOS CONCURRENTS I MEMÒRIA COMPARTIDA
Exemple 2: Two people, one account – Codi resultant
private void reMrarDiners (int quanMtat) { If (compte.mostrarSaldo() >= quanMtat) {
System.out.println(Thread.currentThread().getName() +" està a punt de reMrar diners"); try {
System.out.println (Thread.currentThread().getName() +" s'adormirà"); Thread.sleep(500);
} catch(InterruptedExcepMon ex) {ex.printStackTrace(); } System.out.println (Thread.currentThread().getName() + " ja s'ha despertat"); compte.reMrar(quanMtat); System.out.println (Thread.currentThread().getName() + " completa la reMrada"); }
else { System.out.println("Ho senMm,no hi ha fons per " + Thread.currentThread().getName());
} }
}
PROCESSOS CONCURRENTS I MEMÒRIA COMPARTIDA
Exemple 1: Molta llet -‐ Solució
La sincronització permet definir ordres estrictes d’accés i execució.
PROCESSOS CONCURRENTS I MEMÒRIA COMPARTIDA
Cadenats i sincronització
• Cada objecte de Java té un cadenat. Un cadenat té una única clau. La majoria del temps, estan oberts.
• Els cadenats dels objectes entren en joc quan hi ha mètodes sincronitzats. Aquests cadenats és el que coneixerem com a .
• La finalitat de la sincronització és protegir les dades críMques. No protegirem sota cadenat les dades, sincronitzarem els mètodes que accedeixin a aquestes dades.
PROCESSOS CONCURRENTS I MEMÒRIA COMPARTIDA
Exemple 3: El temut problema de l’actualització perduda
Hi ha un altre problema clàssic de concurrència que prové del món de les bases de dades.
L’actualització perduda gira al voltant d’un procés: Pas 1: Agafa el saldo del compte
int i = saldo; Pas 2: Suma 1 a aquest saldo
saldo = i + 1; El truc per mostrar això és forçar l'equip a fer 2 passos per completar el canvi en l'equilibri. En el món real, faríem aquest moviment particular en una sola sentència: saldo++; Ara bé, forçant-ho a 2 passos, poden aparèixer els problemes propis d’un procés no atòmic.
PROCESSOS CONCURRENTS I MEMÒRIA COMPARTIDA
Exemple 3: El temut problema de l’actualització perduda
Imaginem que, enlloc dels passos trivials “obMngues el saldo i suma-‐hi 1”, els dos (o més passos) d’aquest mètode són molt més complexos i, per tant, no es pot fer amb una única sentència.
En el problema de l’actualització perduda, tenim 2 fils, ambdós intentant incrementar el saldo.
PROCESSOS CONCURRENTS I MEMÒRIA COMPARTIDA
Exemple 3: El temut problema de l’actualització perduda class TestSync implements Runnable {
private int saldo;
public void run () { for (int i=0; i<50; i++) { increment (); System.out.println(“El saldo és “ + saldo); } }
public void increment () { int I = saldo; saldo = I + 1;
} }
public class TestSyncTest { public staMc void main (String[] args) { TestSync tasca = new TestSync (); Thread a = new Thread (tasca); Thread b = new Thread (tasca); a.start (); b.start (); } }
Cada fil corre 50 cops, incrementant el saldo
amb cada iteració.
Aquí hi ha el punt crucial!! Incrementem el saldo afegint 1 a sigui quin sigui el
valor del saldo en el moment de llegir-‐ho (enlloc d’afegir 1 a sigui quin sigui el
valor actual).
PROCESSOS CONCURRENTS I MEMÒRIA COMPARTIDA
Exemple 3: El temut problema de l’actualització perduda
El fil A funciona durant un temps: • Posem el valor de saldo en la variable i. • La variable saldo és 0, per tant, i ara és 0. • Actualitzem saldo amb el resultat de i+1. • Ara, saldo és 1. • Posem el valor de saldo en la variable i. • La variable saldo val 1, per tant, i ara és 1. • Actualitzem saldo amb el resultant de i+1. • Ara saldo val 2.
El fil B funciona durant un temps: • Posem el valor de saldo en la variable i. • La variable saldo és 2, per tant, i ara és 2. • Actualitzem saldo amb el resultat de i+1. • Ara, saldo és 3. • Posem el valor de saldo en la variable i. • La variable saldo val 3, per tant, i ara és 3. [Ara el fil B és enviat de tornada cap a runnable; abans de que posi el valor de saldo a 4].
El fil A arrenca de nou a parMr del punt on ho havia deixat: • Posem el valor de saldo en la variable i. • La variable saldo és 3, per tant, i ara és 3. • Actualitzem saldo amb el resultat de i+1. • Ara, saldo és 4. • Posem el valor de saldo en la variable i. • La variable saldo val 4, per tant, i ara és 4. • Actualitzem saldo amb el resultant de i+1. • Ara saldo val 5.
El fil B arrenca de nou a parMr del punt on ho havia deixat: • Posem el valor de saldo en la variable i. • Ara, saldo és 4.
Ep! El fil A el va actualitzar a 5, però ara ha tornat el B al començament de l’actualització de l’A com si aquest no hagués fet res!!
1 2
3 4
PROCESSOS CONCURRENTS I MEMÒRIA COMPARTIDA
Exemple 3: El temut problema de l’actualització perduda: Solució
Sincronitzant el mètode increment () solucionem el problema de l’actualització perduda” perquè manté els dos passos en el mètode com una única i sòlida unitat. public void increment () {
int i = saldo; saldo = I + 1;
} }
PROCESSOS CONCURRENTS I MEMÒRIA COMPARTIDA
Exemple 4: Interferència entre fils
class Counter { private int c = 0; public void increment() { c++; } public void decrement() { c--; } public int valor() { return c; } } c++ està compost de: 1) Obtenir el valor de c 2) Incrementar c en 1 3) Emmagatzemar el valor de c c-‐-‐ està compost de: 4) Obtenir el valor de 5) Decrementar c en 1 6) Emmagatzemar el valor de c
Dos fils A i B poden espatllar-ho:
Fil A: Recuperar c (0) Fil B: Recuperar c (0) Fil A: Increment c (1) Fil B: Decrement c (-1) Fil A: Emmagatzemar c (1) Fil B: emmagatzemar c (-1)
PROCESSOS CONCURRENTS I MEMÒRIA COMPARTIDA
Exemple 4: Interferència entre fils
class Counter { private int c = 0; public synchronized void increment() { c++; } public synchronized void decrement() { c--; } public int valor() { return c; } }
PROCESSOS CONCURRENTS I MEMÒRIA COMPARTIDA
Exemple 3: El temut problema de l’actualització perduda El fil A funciona durant un temps: • Prova d’accedir al mètode increment (). • El mètode està sincronitzat; agafa la clau
per aquest objecte. • Posem el valor de saldo en la variable i. • La variable saldo és 0 i, ara i és 0. • Posem a saldo el resultat de i + 1. • Ara saldo val 1. • Retornem la clau (això completa el mètode
increment (). • Tornem a entrar en el mètode increment () i
agafem la clau. • Posem el valor de saldo en la variable i. • Ara saldo val 1; per tant, ara i val 1. [Ara el fil A és retornat a runnable, però com que no ha completat el mètode sincronitzat, manté la clau]
El fil B és seleccionat per funcionar: • Prova d’accedir al mètode increment ().
Com que el mètode està sincronitzat, necessitem agafar la clau.
• La clau no es troba disponible. [Ara el fil B és enviat a una “sala de cadenats d’objecte no disponible”].
1 2
3
4
El fil B arrenca de nou a parMr del punt on ho havia deixat (encara té la clau): • Posa a saldo el resultat de i+1. • Ara, saldo és 2. • Retorna la clau. [Ara el fil A és retornat a runnable però, com que ja ha completat el mètode increment (), el fil no manté la clau].
El fil B és seleccionat per funcionar: • Prova d’accedir al mètode increment (). El mètode està sincronitzat.
Necessitem la clau. • Aquest cop, la clau està disponible. L’agafem. • Posem el valor de saldo en la variable i. [ConMnua funcionant.. ]
És una bona idea sincronitzar-‐ho tot? Per si de cas..
• No; els mètodes sincronitzats comporten despeses generals. Quan el codi arriba a un mètode sincronitzat, hi ha un impacte en el rendiment – normalment, no es percep – mentre es confirma si “la clau del cadenat” està disponible.
• Un mètode pot alenMr el programa per les restriccions que defineix. Un mètode sincronitzat força a la resta de fils a estar disponibles i esperar el seu torn.
• Els mètodes sincronitzats poden donar lloc a un punt mort.
Una regla d’or a seguir és sincronitzar només el mínim del que
ha d'estar sincronitzat.
PROCESSOS CONCURRENTS I MEMÒRIA COMPARTIDA
Una regla d’or a seguir és sincronitzar només el mínim del que ha d'estar sincronitzat.
public void go () { fesFeina ();
synchronized(this) {
feinaCriMca (); mesFeinaCriMca (); }
}
fesFeina () no necessita ser sincronitzat; per tant, no sincronitzarem tot el mètode sencer.
Ara, només aquestes dues crides als mètodes s’agrupen en una unitat atòmica. Quan s'uMlitza la paraula clau synchronized dins d'un mètode, enlloc d'una declaració de mètode, hem de proporcionar un argument que és l'objecte la clau del qual el fil ha d’aconseguir. Tot i que hi ha altres maneres per fer-‐ho,
quasi sempre sincronitzarem l’objecte actual (this). De fet, és el mateix objecte que tancariem (lock) si sincronitzessim el mètode sencer.
PROCESSOS CONCURRENTS I MEMÒRIA COMPARTIDA
Codi sincronitzat
• Enlloc de sincronitzar tot un mètode podem sincronitzar una porció de codi.
public void addName(String name) {
synchronized(this); {
lastName=name; nameCount++;
} nameList.add(name);
}
PROCESSOS CONCURRENTS I MEMÒRIA COMPARTIDA
• Java uMlitza monitors per la sincronització de fils.
• Tot objecte amb mètodes és un monitor.
• El monitor solament permet a un fil per ocasió executar un mètode sobre l’objecte.
• Los mètodes que hagin d’accedir al mateix objecte, s’han de declarar per aconseguir el bloqueig.
• L’execució de dos mètodes sincronitzats és mútuament excloent.
PROCESSOS CONCURRENTS I MEMÒRIA COMPARTIDA
//Aquest programa no està sincronitzat. class Crida {
void crit (String msg){ System.out.print("["+msg); try { Thread.sleep(1000); }catch(InterruptedException e) {;} System.out.print("]"); }
}
public class ElQueCrida extends Thread { String msg; Crida objectiu; Thread t; public ElQueCrida(Crida objectiu, String msg) { this.objectiu = objectiu; this.msg = msg; t = new Thread(this); t.start(); } public void run() { objectiu.crit(msg); }
}
class Sincro { public static void main(String[] args){ Crida objectiu = new Crida();
ElQueCrida ob1 = new ElQueCrida(objectiu, "Hola"); ElQueCrida ob2 = new ElQueCrida(objectiu, "Mon"); ElQueCrida ob3 = new
ElQueCrida(objectiu, "Sincronitzat");
//Esperem a que els fils acabin try { ob1.t.join(); ob2.t.join(); ob3.t.join(); }catch(InterruptedException e){;}
} }
On haig d’aplicar el monitor?
PROCESSOS CONCURRENTS I MEMÒRIA COMPARTIDA
Sincronització: Monitors – Exemple //Aquest programa no està sincronitzat. class Crida {
void crit (String msg){ System.out.print("["+msg); try { Thread.sleep(1000); }catch(InterruptedException e) {;} System.out.print("]"); }
}
public class ElQueCrida extends Thread { String msg; Crida objectiu; Thread t; public ElQueCrida(Crida objectiu, String msg) { this.objectiu = objectiu; this.msg = msg; t = new Thread(this); t.start(); } public void run() { objectiu.crit(msg); }
}
class Sincro { public static void main(String[] args){ Crida objectiu = new Crida();
ElQueCrida ob1 = new ElQueCrida(objectiu, "Hola"); ElQueCrida ob2 = new ElQueCrida(objectiu, "Mon"); ElQueCrida ob3 = new
ElQueCrida(objectiu, "Sincronitzat");
//Esperem a que els fils acabin try { ob1.t.join(); ob2.t.join(); ob3.t.join(); }catch(InterruptedException e){;}
} }
On haig d’aplicar el monitor? class Crida {
synchronized void crit(String msg){ //…
}
PROCESSOS CONCURRENTS I MEMÒRIA COMPARTIDA
§ (Fins ara..) Casos/Exemples amb fils asíncrons i independents § Cada fil conté totes les dades i els mètodes necessaris i no
requereix recursos externs. § Els fils s’executen en el seu propi espai; no els influeix l’estat
o l’acMvitat d’altres fils que s’execuMn de forma concurrent. § (Noves situacions) Fins concurrents que comparteixen dades i han de considerar l’estat i l’acMvitat d’altres fils. § Escenaris “productor/consumidor”.
PROCESSOS CONCURRENTS I MEMÒRIA COMPARTIDA
§ Escenari “productor – consumidor” § El PRODUCTOR genera un canal de dades que és consumit pel CONSUMIDOR.
Productor Consumidor
PROCESSOS CONCURRENTS I MEMÒRIA COMPARTIDA
• Podem imaginar una aplicació JAVA on un fil (PRODUCTOR) escriu dades en un arxiu mentre que un segon fil (CONSUMIDOR) llegeix les dades del mateix arxiu.
• si teclegem caràcters en el teclat, el fil PRODUCTOR situa les polsacions en una pila d’esdeveniments i el fil CONSUMIDOR llegeix els esdeveniments de la mateixa pila.
• Aquests dos exemples uMlitzen fils concurrents que comparteixen un recurs comú; el primer comparteix un arxiu i el segon una pila d’esdeveniments.
• Com que els fils comparteixen un recurs comú, s’han de sincronitzar.
PROCESSOS CONCURRENTS I MEMÒRIA COMPARTIDA
§ El PRODUCTOR genera un enter entre 0 i 9, l’emmagatzema en un objecte i imprimeix el número generat.
§ A més, el PRODUCTOR dorm durant un temps aleatori entre 0 i 100 milisegons abans de repeMr el cicle de generació de números.
EXEMPLE
PROCESSOS CONCURRENTS I MEMÒRIA COMPARTIDA
§ Com que el CONSUMIDOR està famèlic, consumeix tots els enters de l’objecte
tant ràpid com sigui possible – quan el número generat està disponible, l’agafa –.
EXEMPLE
Productor Consumidor
PROCESSOS CONCURRENTS I MEMÒRIA COMPARTIDA
§ El PRODUCTOR i el CONSUMIDOR comparteixen dades per mitjà d’un objecte en comú ( ).
§ Cap dels dos fa cap esforç per assegurar-‐se de que el CONSUMIDOR obMngui cada valor produït un únic cop.
§ La sincronització entre aquests dos fils es dóna en un nivell inferior, dins dels mètodes get() i put() de l’objecte .
PROCESSOS CONCURRENTS I MEMÒRIA COMPARTIDA
I si els fils no esMguessin sincronitzats?
. . . Consumidor #1 obté: 3 Productor #1 posa: 4 Productor #1 posa: 5 Consumidor #1 obté: 5 . . .
• Un problema seria quan el PRODUCTOR fos més ràpid que el CONSUMIDOR i generés dos números abans de que el CONSUMIDOR Mngués una possibilitat de consumir el primer número. Així, el CONSUMIDOR es saltaria un número.
• Part de la sorMda seria..:
PROCESSOS CONCURRENTS I MEMÒRIA COMPARTIDA
I si els fils no esMguessin sincronitzats?
. . . Productor #1 posa: 4 Consumidor #1 obté: 4 Consumidor #1 obté: 4 Productor #1 posa: 5 . . .
• Un altre problema que podria aparèixer seria si el CONSUMIDOR fos més ràpid que el PRODUCTOR i consumís el mateix valor dos o més cops.
• En conseqüència, el CONSUMIDOR imprimiria el mateix valor dos cops.
• Part de la sorMda seria..:
PROCESSOS CONCURRENTS I MEMÒRIA COMPARTIDA
• Per prevenir els problemes que sorgeixen en l ’ e x e m p l e P R O D U C T O R / C O N S U M I D O R , l’emmagatzematge d’un nou enter a CubbyHole per part del PRODUCTOR ha d’estar sincronitzat amb la recuperació per part de CONSUMIDOR.
• El programa PRODUCTOR/CONSUMIDOR uMlitza dos mecanismes diferents per sincronitzar els fils: els monitors i els mètodes noKfy() i wait().
PROCESSOS CONCURRENTS I MEMÒRIA COMPARTIDA
• El programa PRODUCTOR/CONSUMIDOR uMlitza dos mecanismes diferents per sincronitzar els fils: els monitors i els mètodes noKfy() i wait().
PROCESSOS CONCURRENTS I MEMÒRIA COMPARTIDA
• A Java s’associa un únic monitor per cada objecte que té un mètode sincronitzat.
• La classe té 2 mètodes sincronitzats: ü El mètode put() s’uMlitza per
canviar el valor de CubbyHole. ü El mètode get() s’uMlitza per
recuperar el valor actual.
• És així com el sistema associa un únic monitor amb cada exemplar de CubbyHole.
Sincronització dels fils
PROCESSOS CONCURRENTS I MEMÒRIA COMPARTIDA
El mateix fil pot cridar a una mètode sincronitzat d’un objecte pel que ja té el monitor, és a dir, pot tornar a adquirir el monitor.
• Sempre que el PRODUCTOR crida al mètode put() de CubbyHole , adquireix el monitor de l’objecte CubbyHole, i així evita que el consumidor pugui cridar al mètode get() de CubbyHole.
• E l m è t o d e wa i t ( ) a l l i b e r a temporalment el monitor.
• Quan el mètode put() retorna, el PRODUCTOR allibera el monitor i desbloqueja l’objecte CubbyHole.
PROCESSOS CONCURRENTS I MEMÒRIA COMPARTIDA
El mateix fil pot cridar a una mètode sincronitzat d’un objecte pel que ja té el monitor, és a dir, pot tornar a adquirir el monitor.
• Sempre que el CONSUMIDOR crida al mètode get() de CubbyHole , adqui re ix e l monitor d’aquest objecte i, per tant, evita que el PRODUCTOR pugui cridar al mètode put().
PROCESSOS CONCURRENTS I MEMÒRIA COMPARTIDA
Els mètodes i poden ser invocats únicament des de dins d’un mètode
sincronitzat o dins d’un bloc o una sentència sincronitzada.
• Mètode El mètode fa que el fil actual esperi (probablement, per sempre) fins que un altre fil li ho noMfiqui o que canviï una condició. El mètode s’uMlitza en conjunció amb el mètode per coordinar l’acMvitat de varis fils que uMlitzen els mateixos recursos. Quan el fil entra en el mètode , el monitor és alliberat automàMcament, i quan el fil surt del mètode , s’adquereix de nou el monitor.
PROCESSOS CONCURRENTS I MEMÒRIA COMPARTIDA
Els mètodes i poden ser invocats únicament des de dins d’un mètode
sincronitzat o dins d’un bloc o una sentència sincronitzada.
• Mètode El mètode escull un fil que està esperant el monitor posseït pel fil actual i el desperta. Normalment, el fil que es troba a l’espera, capturarà el monitor i procedirà.
• Mètode El mètode permet desbloquejar tots els fils que s’hagin bloquejat amb la invocació del mètode . Quan envia la noMficació a tots els fils, aquests comencen a compeMr per tancar el candau i, en conseqüència, per executar-‐se.
Normalment, optem per uKlitzar en detriment de donat que no sabem es pot donar una situació de coexistència de varis fils en el futur.
PROCESSOS CONCURRENTS I MEMÒRIA COMPARTIDA
• El fil CONSUMIDOR crida al mètode get( ) , pe l que e l mètode CONSUMIDOR posseeix el monitor de CubbyHole durant l’execució del mètode get().
• Al final del mètode get(), la crida al mètode noKfy() desperta al fil PRODUCTOR que obté el monitor de CubbyHole i procedeix.
• Si existeixen varis fils esperant per un monitor, el sistema d’execució Java escull un d’aquests fils sense cap compromís ni garanMa sobre el fil que serà escollit.
PROCESSOS CONCURRENTS I MEMÒRIA COMPARTIDA
• El bucle while conté la crida a wait(). Aquest mètode espera indefinidament fins que arribi una noMficació del fil PRODUCTOR.
• Quan el mètode put() crida a noKfy(), el CONSUMIDOR desperta de l’estat d’espera i conMnúa amb el bucle.
• Presumiblement, el PRODUCTOR ja ha generat un nou número i el métode get() cau al final del bucle i procedeix.