Concorrenza (cenni)
Docente: Gabriele Lombardi
[email protected]
© 2010 - CEFRIEL
The present original document was produced by CEFRIEL and the Teacher for the benefit and internal use of this
course, and nobody else may claim any right or paternity on it. No right to use the document for any purpose other than
the Intended purpose and no right to distribute, disclose, release, furnish or disseminate it or a part of it in any way or
form to anyone without the prior express written consent of CEFRIEL and the Teacher.
© copyright Cefriel and the Teacher-Milan-Italy-23/06/2008. All rights reserved in accordance with rule of law and
international agreements.
© 2010 - CEFRIEL
Sommario
SLIDE
CONTENUTO
I Thread
Strumenti di gestione dei thread.
Object
Cosa ci da Object? Lock, wait/notify.
Pattern
Alcuni pattern di sincronizzazione.
Executors & Co.
Strumenti di java.util.concurrent.*
© 2010 - CEFRIEL
I Thread

Rappresentano:
– flussi di esecuzione paralleli tra loro;
– operanti automaticamente su memoria condivisa;
– sincronizzabili tra loro con appositi costrutti.

Come si usano:
– modo 1 (di solito errato):
• estendendo la classe Thread;
• effettuando l’override del metodo “run”;
– modo 2 (di solito corretto):
• implementando l’interfaccia Runnable;
• istanziando la classe Thread e passando al suo
costruttore il Runnable come parametro.
– ad ogni modo:
• start, stop, suspend, resume, join, yield, …
© 2010 - CEFRIEL
I Thread

Quali esistono inizialmente:
– il nostro (che inizia a lavorare dal main);
– quello di GC (Garbage Collection);
– quello della JVM.

Quali problematiche devono essere affrontate …
– … per scrivere codice concorrente:

• accesso concorrente (race-conditions);
• sincronizzazione tra processi;
• atomicità delle operazioni;
• comunicazione tra thread.
Nella sintassi Java abbiamo strumenti per
affrontare questi problemi tramite patterns.
© 2010 - CEFRIEL
Strumenti in Object

In ogni Object sono “nascosti”:
– una coda di attesa dei thread bloccati;
– un semaforo di risorsa disponibile o no;

scopo di questi strumenti:
– implementare il Monitor pattern:
– un oggetto può sincronizzare le chiamate:
• ai metodi, così che un thread alla volta li esegua;
• a porzioni di codice, con lo stesso significato;
– effetto “dietro le quinte”:
• il semaforo dell’oggetto corrente:
– viene controllato atomicamente;
– provoca l’ingresso in wait-list se rosso;
– diviene rosso se verde, permettendo l’ingresso al thread.
• i thread che “trovano il semaforo rosso”:
– vengono messi in attesa.
© 2010 - CEFRIEL
Un esempio concreto
Aggiungere 100
Leggo “Conto”  100
Togliere 100
Conto
100
 fermati dallo scheduler 
Conto
0
 tocca a noi 
Aggiungo 100 (ora ho 200)
Scrivo “Conto”  200
 Semaforo rosso 
Leggo “Conto”  100
Aggiungo 100 (ora ho 200)
Scrivo “Conto”  200
 Semaforo verde 
 tocca a noi 
Leggo “Conto”  100
Tolgo 100 (ora ho 0)
Scrivo “Conto”  0
Conto
200
Conto
100
Conto
200
Conto
100
© 2010 - CEFRIEL
 Semaforo rosso 
Leggo “Conto”  200
Tolgo 100 (ora ho 100)
Scrivo “Conto”  100
 Semaforo verde 

In Java:
Un esempio concreto
– la parola synchronized crea una sezione critica!
public class ContoCorrente { // Siamo figli di Object!
// Risorsa condivisa:
private int conto;
// Metodo sincronizzato sul semaforo:
public synchronized void addebita(int qta) {
// Operazione atomica:
conto -= qta;
}
// Metodo sincronizzato sul semaforo:
public void accredita(int qta) {
synchronized (this) {
// Operazione atomica:
conto += qta;
}
}
}
© 2010 - CEFRIEL
Attendere e notificare

Perché attendere e notificare?
–
–
–
–
–

È una forma di comunicazione tra thread;
permette di gestire la sincronizzazione tra processi;
chiamate bloccanti senza busy waiting;
è più granulare dell’utilizzo di “synchronized”;
è meno semplice da utilizzare.
Come funziona?
– in object sono presenti:
• dei metodi di wait:
– mettono il processo attuale in attesa di notifiche …
– … sulla coda dell’oggetto corrente;
• dei metodi notify:
– notificano il liberamento della risorsa …
– … ai thread in attesa sull’oggetto corrente.
– in particolare:
• possono essere chiamate solo in un blocco
synchronized (a semaforo rosso);
• durante il periodo di wait il semaforo resta verde.
© 2010 - CEFRIEL
Esempio producer-consumer
public class Producer { // Produttore
Consumer cons;
// Riferimento al consumatore
public Producer(Consumer c) { cons = c; }
public void produce() { cons.consume(1); } }
public class Consumer { // Consumatore runnabile
// Cose da consumare:
Queue queue = new LinkedList<Integer>();
public synchronized void consume(int num) {
// Accodo ciò che va consumato:
queue.add(num);
Esempio di
produttore
(push)
Esempio di
consumatore
basato su
coda prodotti
// Notifico chi aspetta:
notify(); }
vedere l’esempio completo in
public void run() {
Code\04_Concurrency\ProducerConsumer
while(true) {
// Attendo un dato:
synchronized(this) {
while (queue.size()==0) wait(1000); }
// Lo consumo:
doSomething(queue.poll());
}}
}
© 2010 - CEFRIEL
Pattern per la concorrenza

Cosa sono?
– Soluzioni strutturali preconfezionate, da applicare come buone
norme di strutturazione del codice;
– Nome + problema + soluzione consigliata + esempi.

Dove trovare informazioni:
– “Google is your friend” (dicevano sui forum);
– “Patterns for Parallel Computing”;
– forum, articoli scientifici, API J2SE, …

In generale:
– cercare prima tra le API del JDK per controllare:
• se sono già implementati (e come si usano);
• se no, quali strumenti conviene utilizzare;
– seguire alla lettera la descrizione (con coscienza di causa);
– dimostrare (a sé stessi) il funzionamento del sistema.
© 2010 - CEFRIEL
Pattern per la concorrenza

Monitor:
– ottenere una entità unica ad accesso esclusivo;
– già visto, già implementato nel linguaggio Java;

PE (Process Executor):
– esecuzione di operazioni in maniera indipendente dal “chi” e
“quando” opererà di preciso (circa);
– implementato in java.util.concurrent.Executor;
– comprende il concetto di ThreadPool;

Barrier:
– punto di sincronizzazione di più thread (con join solo 2);
– basato sul conteggio dei thread arrivati in una sessione critica;
– implementato in java.util.concurrent.CyclicBarrier;

Future:
– calcolo asincrono di un risultato … che verrà usato prima o poi;
– implementato in java.util.concurrent.Future;

Shadow-copy, Parallel-loop, Divide-et-impera, …
© 2010 - CEFRIEL
Esempio: parallel-loop

Parallel-for:
– eseguire iterazioni indipendenti parallelamente;
– il numero di thread va scelto oculatamente per evitare un
overhead eccessivo e controproducente;
– una barriera di sincronizzazione va definita al termine.

Struttura:
– basata sul pattern master/worker (su Executor);
– il contenuto del ciclo diviene un oggetto che implementa
Runnable con i dati come parametri;

Esempio:
– ricerca di un pattern su documenti multipli;
– utilizzeremo le API per le regular-expressions:
• java.util.regex.*
– la lentezza dell’accesso a file viene “ammortizzata” dalla
parallelizzazione per ottenere uno strumento più veloce.
© 2010 - CEFRIEL
Ricerca di un pattern
… // Importo ciò che serve.
public class Matcher implements Runnable { // Per essere eseguito deve essere un Runnable
… // Dichiaro attributi e costruttore.
public void run() {
String result = file.getName() + ":\n“, riga; // Inizializzazione del risultato e del reader.
BufferedReader reader = null;
try { // Potrei fallire per problemi di IO.
reader = new BufferedReader( // Apertura safe dello stream da file.
new InputStreamReader(new FileInputStream(file)));
int numRiga = 0; // Itero sulle righe:
while ((riga=reader.readLine())!=null) {
numRiga++; // Prossima riga.
if (pattern.matcher(riga).matches()) // Se matcha la aggiungo al risultato:
result += numRiga + " --> \t" + riga + "\n";
} // Ciclo
} // try
catch (Exception e) { … } // Quando le cose vanno male… !
finally { // Con finally eseguo la chusura del reader in ogni caso.
try { if (reader!=null) reader.close(); } catch (Exception e) {}
}
// Solo ora (una volta) accedo concorrentemente alla lista:
synchronized (matches) { matches.add(result); }
}}
© 2010 - CEFRIEL
Matcher.java
Esecuzione parallela
… // Importo ciò che serve.
public class Grep {
public static void main(String[] args) { // Contiene solo il main.
… // Controllo e ottengo gli argomenti.
try { // Inizializzazione:
Pattern pattern = Pattern.compile(".*(" + args[0] + ").*");
List<String> results = new LinkedList<String>();
Grep.java
// Creo l'executor che internamente gestirà i thread multipli (workers):
ExecutorService executor = Executors.newCachedThreadPool();
for (int i=1; i<args.length; i++) { // Itero sui nomi di file (parallel-for).
File file = new File(args[i]); // Ottengo un file.
// Ottengo il worker per questo file:
Matcher worker = new Matcher(results, file, pattern);
// Eseguo parallelamente il matching.
executor.execute(worker);
}
vedere l’esempio completo in
04_Concurrency\PatternMatcher
// Barriera di sincronizzazione (aspetto la terminazione):
executor.shutdown(); executor.awaitTermination(5, TimeUnit.MINUTES);
for (String result: results) System.out.println(result); // Finito, mostro i risultati.
} … // Cattura eccezioni.
}}
© 2010 - CEFRIEL
java.util.concurrent.*

Cosa offre?
– Strutture dati thread-safe (concorrenti) …
• … anche “in salsa” bloccante;
– l’astrazione degli esecutori (thread pool e altri);
• vista nell’esercizio precedente;
• è una generalizzazione del pattern master/worker,
base di molte architetture;
– astrazione per la definizione di task schedulabili;
– tool per implementare pattern di sincronizzazione:

• Semaphore, Exchanger, CyclicBarrier, …
Esempio di utilizzo di Semaphore:
– Quando? 
numero noto di risorse (limitate) da proteggere
da accessi concorrenti (con eventuali chiamate bloccanti);
– Come? 
Semaphore sem = new Semaphore(N);
… sem.acquire(); // Bloccane;
… sem.release(); // Sblocca gli altri.
© 2010 - CEFRIEL

Libri consigliati:
Da qui?
– “Patterns for Parallel Programming”; Addison Wesley;
T.G.Mattson, B.A.Sanders, B.L.Massingill;
– “Designing Concurrent, Distributed, and Real-Time Applications
with UML”; Addison Wesley; Hassan Gomaa;

Tecniche per non sbagliare:
– sviluppare le architetture parallele a parte:
• il codice di gestione dell’architettura parallela è più
comprensibile se esaminato da solo;
• strumenti più generali e astratti scaturiscono dallo
sviluppo isolato dei moduli;
– sviluppare sempre le suite di test (ad esempio con JUnit);
– programmazione difensiva:
• come se l’utilizzatore “volesse fregarci”;
– codice di testing “attaccante”:
• cercando di “fregarci da soli”.
vedere 04_Concurrency\Chat con:
esempio di concorrenza, di networking, di progetti per una libreria, un client e un server
© 2010 - CEFRIEL
Scarica

05_Concurrency