© CEFRIEL Concorrenza (cenni) Docente: Gabriele Lombardi
© 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.
© CEFRIEL Sommario SLIDECONTENUTO 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.*
© 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 loverride del metodo run; –modo 2 (di solito corretto): implementando linterfaccia Runnable; istanziando la classe Thread e passando al suo costruttore il Runnable come parametro. –ad ogni modo: start, stop, suspend, resume, join, yield, …
© 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.
© 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 delloggetto corrente: –viene controllato atomicamente; –provoca lingresso in wait-list se rosso; –diviene rosso se verde, permettendo lingresso al thread. i thread che trovano il semaforo rosso: –vengono messi in attesa.
© CEFRIEL Un esempio concreto Conto 100 Aggiungere 100Togliere 100 Leggo Conto 100 fermati dallo scheduler tocca a noi Aggiungo 100 (ora ho 200) Scrivo Conto 200 tocca a noi Leggo Conto 100 Tolgo 100 (ora ho 0) Scrivo Conto 0 Conto Conto 100 Semaforo rosso Leggo Conto 100 Aggiungo 100 (ora ho 200) Scrivo Conto 200 Semaforo verde Semaforo rosso Leggo Conto 200 Tolgo 100 (ora ho 100) Scrivo Conto 100 Semaforo verde Conto 200 Conto 100
© CEFRIEL Un esempio concreto 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; } In Java: –la parola synchronized crea una sezione critica!
© 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 dellutilizzo 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 delloggetto corrente; dei metodi notify: –notificano il liberamento della risorsa … –… ai thread in attesa sulloggetto corrente. –in particolare: possono essere chiamate solo in un blocco synchronized (a semaforo rosso); durante il periodo di wait il semaforo resta verde.
© 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); } } Esempio di produttore (push) public class Consumer { // Consumatore runnabile // Cose da consumare: Queue queue = new LinkedList (); public synchronized void consume(int num) { // Accodo ciò che va consumato: queue.add(num); // Notifico chi aspetta: notify(); } public void run() { while(true) { // Attendo un dato: synchronized(this) { while (queue.size()==0) wait(1000); } // Lo consumo: doSomething(queue.poll()); } } } Esempio di consumatore basato su coda prodotti vedere lesempio completo in Code\04_Concurrency\ProducerConsumer
© 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.
© 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, …
© 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 dellaccesso a file viene ammortizzata dalla parallelizzazione per ottenere uno strumento più veloce.
© 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); } } Matcher.java
© CEFRIEL 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 results = new LinkedList (); // 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); } // 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. } Grep.java vedere lesempio completo in 04_Concurrency\PatternMatcher
© CEFRIEL java.util.concurrent.* Cosa offre? –Strutture dati thread-safe (concorrenti) … … anche in salsa bloccante; –lastrazione degli esecutori (thread pool e altri); vista nellesercizio 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.
© CEFRIEL Da qui? Libri consigliati: –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 dellarchitettura 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 lutilizzatore 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