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