Estratto del documento

Introduzione alla programmazione concorrente

Mercoledì 21 febbraio 2018 14:11

Motivazioni principali per scrivere programmi concorrenti

Ci sono diverse ragioni per cui la programmazione concorrente è importante:

  • Tante cose da fare e processore abbastanza potente
    • Concorrenza virtuale
    • Processi cooperanti
  • Per modellare il parallelismo del mondo reale
    • Quando si hanno più processori, sfrutta parallelismo reale
    • Sequenziale vs concorrente
  • Batch processing: dati di input disponibili preventivamente
    • Programmi interattivi: periodi in cui il programma aspetta che l'utente fornisca un dato per elaborazione

Parallelismo nel mondo reale

Fare tante cose contemporaneamente è una caratteristica del parallelismo nel mondo reale. La Legge di Moore afferma che la capacità dei calcolatori aumenta ogni 18 mesi.

I processi concorrenti devono programmare e coordinare le loro azioni, il che può portare a nuove condizioni di errore:

  • Deadlock: ciascuna attività concorrente è in attesa del risultato di un'altra attività per eseguire
  • Race condition: due o più attività simultanee tentano di aggiornare lo stesso oggetto
  • Starvation: a una o più attività simultanee vengono continuamente negate risorse

Proprietà desiderate

  • Safety: le attività concorrenti non interferiscono tra di loro col rischio di corrompere i dati
  • Liveness: le attività concorrenti devono fare progressi con le loro attività senza deadlock o starvation

Programmazione in ambiente distribuito

Componenti che girano contemporaneamente su più computer utilizzano:

  • Socket
  • Remote Method Invocation (RMI)
    • Implementa RPC
    • Client può invocare un metodo su un oggetto remoto in modo trasparente

Organizzazione client-server

  • Server: entità che mette a disposizione delle risorse o servizi
  • Client: entità che richiede risorse e servizi al server
  • Le richieste e risposte seguono un protocollo, insieme di regole

Programmazione a eventi

Utile per la gestione di interfacce a oggetti.

Gli stream di I/O

Mercoledì 21 febbraio 2018 16:03

Gestione di ingresso e uscita

Il pacchetto java.io definisce le operazioni di gestione di ingresso e uscita attraverso flussi (Stream):

  • Flussi di Input/Output: sequenze ordinate di dati, hanno una sorgente o una destinazione
    • Sorgente può essere in memoria, disco o remota

Il pacchetto è composto da due sezioni principali:

  • Flussi di Byte
    • Unità di informazione è il byte
    • I/O binario, dati tutti in formato binario
    • Classi indicate come: Stream di ingresso e Stream di uscita
  • Flussi di Caratteri
    • Unità di informazione sono caratteri Unicode
    • I/O testuale
    • Classi indicate come: Lettori e Scrittori

Errori

Se operazioni di ingresso o uscita non vanno a buon fine, vengono segnalati in due modi:

  • Cambiando lo stato dello stream
  • Lanciando un'eccezione di tipo IOException

Ingresso e scrittura di file binari

  • Si utilizza FileInputStream [ FileInputStream in = new FileInputStream(nomeFile); ]
  • Per leggere un byte si usa il metodo read() che restituisce int con byte letto
  • Quando non c'è più nulla da leggere read restituisce -1
  • Scrittura su file binari si utilizza FileOutputStream [ FileOutputStream out = new FileOutputStream(nomeFile); ]
  • Per scrivere un byte si usa il metodo write(int c)

Lettura di un file di testo

Si utilizza FileReader.

Thread e multi-thread

Lunedì 26 febbraio 2018 11:10

Definizione e differenze

Un programma è un insieme di istruzioni di alto livello o in linguaggio macchina. Un processo è un programma in esecuzione, possibile più processi in parallelo grazie a scheduler.

  • Se i processi condividono lo stesso spazio degli indirizzi → processi leggeri o thread
  • Se i processi hanno il proprio spazio degli indirizzi → processi pesanti o processi

Programmazione concorrente

Pratica di implementare programmi che contengono più flussi di esecuzione (thread), creando thread per ogni richiesta.

Programmi concorrenti e sequenziali

  • Programma sequenziale è deterministico
  • Programma concorrente: il comportamento di un thread dipende da un altro thread

Modello di memoria semplificato (SMM)

Utilizza diversi tipi di memoria:

  • Heap
  • Method area
  • Program context

Sleeping e blocking

Spesso un thread può essere sospeso per un certo tempo, assumendo lo stato di sleeping o blocked. Resta in questo stato per il tempo precisato poi torna ready.

  • Per far partire i thread si utilizza start
  • Per far sì che dopo una start inizi ad eseguire il task t, si manda in sleep il metodo main

Sleep()

Metodo che mette in attesa un processo senza consumare cicli del processore. È possibile interrompere uno sleep via interrupt, quindi sleep va inserito in un try-catch.

  • Testare se il metodo è vivo: isAlive()
  • Attendere la terminazione del thread: Join()
  • Per fermare un thread si usa un interrupt
  • Usando il flag dell'interrupt e sollevando l'eccezione InterruptException
  • Permette ad un thread di cedere il processore: Yield()

Thread e multi-thread

Lunedì 23 aprile 2018 14:11

Creare un thread

  1. Estendere la classe java.lang.Thread
  2. Riscrivere (Override) il metodo run()
  3. Creare istanza della sottoclasse
  4. Richiamare il metodo start() su questa istanza

Limitazione: le classi che estendono Thread non possono estendere altre classi.

Il metodo run()

Costituisce l'entry point del thread.

  • Thread Alive → finché run() non ritorna
  • Thread Dead → quando run() ritorna

Dopo che un thread è Dead non può essere rieseguito, se no si deve creare una nuova istanza. Non si può far partire lo stesso thread più volte.

Approccio alternativo alla creazione di un thread

  1. Definire implementazione Runnable
  2. Realizzare il metodo run()
  3. Creare istanza
  4. Instanziare un nuovo Thread passando al costruttore l'istanza che implementa Runnable
  5. Richiamare start() su Thread

Programmi concorrenti e sequenziali

Programmi concorrenti hanno proprietà molto diverse dai programmi sequenziali: questi ultimi riproducono lo stesso input ogni volta, mentre nei programmi concorrenti il comportamento di un thread dipende da un altro thread.

  • Il metodo run() può essere chiamato più volte
  • Il metodo start() può essere chiamato solo una volta

Flusso di controllo

Se ci sono due flussi di esecuzione e l'oggetto A non aspetta che termini l'esecuzione dell'oggetto B, il programma termina quando tutti i thread non daemon terminano. Un thread daemon fornisce un servizio generale in background mentre il programma esegue altro.

Thread daemon

  • Priorità molto bassa, eseguiti quando nessun altro thread è in esecuzione
  • Utilizzati come fornitori di servizi per i thread normali
  • JVM termina il programma terminando i thread daemon quando sono gli unici in esecuzione

Stati di un thread

Born, Ready, Running, Dead. Invochiamo il metodo start → thread diventa ready. Quando viene selezionato dallo scheduler diventa Running ed esegue run().

Java thread scheduling

Come funziona lo scheduler? JVM schedula l'esecuzione dei thread utilizzando un algoritmo di scheduling preemptive e priority based. Tutti i thread hanno una priorità e il thread con la priorità più alta tra quelli ready viene schedulato per essere eseguito. Con il diritto di preemption lo scheduler sottrae la CPU al processo e assegna al nuovo processo.

Politica di scheduler

Java non specifica il tipo di politica, dipende dal Sistema Operativo. Se i due thread hanno la stessa priorità:

  • In presenza di scheduling non preemptive eseguirà solo il thread lanciato per primo
  • Se vanno entrambi, lo scheduling è preemptive
    • Quando scade il quanto di tempo al thread, viene sottratta CPU e passata al thread

Modello di memoria semplificato (SMM)

Utilizzato dalla Macchina Virtuale Semplificata (SVM) utilizza diversi tipi di memoria:

  • Heap, memorizzare oggetti e loro dati
  • Method Area, definizioni delle classi e istruzioni compilate
  • Program Context, informazioni uniche per thread come stack e PC

SMM durante l'esecuzione di un programma single thread:

  • SVM crea heap e il thread context, method area e carica i metodi
  • Crea il PC con valore non definito
  • SVM inserisce un activation record per il main nello stack
  • SVM assegna la linea di codice dove si istanzia la classe, PCexe n = new PCexe, al PC e al thread PC
  • SVM esegue la riga 10 - crea istanza della classe Pcexe nello heap
  • SVM aggiorna il PC che passa a riga seguente
  • SVM chiama run()
  • SVM crea nuovo activation record per il run() contenente la variabile locale counter
  • Aggiorna il pc che diventa 4
  • *Step 5-6-7* SVM esegue righe da 4 a 6 comprese, stampa variabili, incrementa e stampa
  • PC punta alla linea 7, counter vale 1
  • SVM esegue return, elimina activation record di run dallo stack
  • Main esegue il return e elimina activation record del metodo main

SVM thread states

Un programma concorrente ha più thread, quindi la SVM deve scegliere quale mandare in esecuzione, la decisione dipende da vari fattori.

Sleeping and blocking

Un thread può essere sospeso per un certo periodo di tempo (Thread.sleep). In questo caso, assume lo stato di sleeping o blocked.

  • Sleep() non utilizza cicli del processore
  • Metodo statico e mette in pausa il thread corrente
  • Mentre è in sleep può essere interrotto da un thread o sollevata un'eccezione
  • Interrupt dovrebbe essere usato con wait, non con sleep

Creare un metodo che rallenti l'esecuzione di due thread non è una buona soluzione, si sprecano cicli di processore, inoltre si può avere ritardo rispetto al momento desiderato (Busy Loop()).

isAlive()

Testare se è vivo.

  • Quando viene chiamato start() il thread è alive
  • Thread considerato alive finché non run() non ritorna

Join()

Attende la terminazione del thread sul quale è chiamato.

  • Thread sul quale è chiamato join rimane bloccato in attesa che termini l'altro

Come stoppare un thread?

Non vi è un modo sicuro ed efficace per fermare un thread, esistevano vari metodi ma sono stati deprecati. Per fermare un thread la risposta definitiva è interrupt().

Interrupt()

  • Setta un flag di interruzione nel thread di destinazione e ritorna
  • Thread non viene effettivamente interrotto
  • Thread può controllare se flag è settato e nel caso uscire
  • Metodi che mettono in pausa controllano il flag di interruzione prima dopo e durante
  • Se flag è settato lancia un'eccezione
  • Thread da interrompere intercetta l'eccezione e dovrebbe terminare l'esecuzione

Problemi con interrupt()

Interrupt() non funziona se il thread non esegue mai metodi di attesa, sleep, join. Thread devono cooperare per verificare il suo stato di interruzione.

  • isInterrupted(): controlla il flag senza resettarlo
  • Thread.interrupted(): controlla il flag e se è settato lo resetta

Thread.yield()

Permette ad un thread di lasciare volontariamente il processore ad un altro thread. Utile nel caso di un thread che non esegue spesso operazioni che lo mettano in attesa. Si cede il controllo allo scheduler che sceglie un altro thread da mandare in esecuzione.

  • Quando si usa yield()?
    • Quando non c'è preemption
    • In casi particolari

Correctness

Lunedì 7 maggio 2018 14:37

Non-determinismo

Il numero di diversi possibili percorsi di esecuzione di un programma concorrente è definito come: non-determinismo. Il non-determinismo implica che testare un programma concorrente è solitamente difficile dato che non è possibile determinare l'ordine assoluto dell'esecuzione delle istruzioni.

Questi problemi sono definiti come race condition: tutte quelle situazioni in cui thread diversi operano su una risorsa comune ed in cui il risultato viene a dipendere dall'ordine in cui essi effettuano le loro operazioni.

Le race condition si possono verificare in due condizioni

  • Una risorsa deve essere condivisa tra due thread
  • Deve esistere almeno un percorso di esecuzione tra i thread in cui una risorsa è condivisa in modo non sicuro

Dimostrare la correttezza di un programma concorrente

  • Dimostrare proprietà di sicurezza e vivacità
Anteprima
Vedrai una selezione di 7 pagine su 28
Programmazione concorrente e distribuita Pag. 1 Programmazione concorrente e distribuita Pag. 2
Anteprima di 7 pagg. su 28.
Scarica il documento per vederlo tutto.
Programmazione concorrente e distribuita Pag. 6
Anteprima di 7 pagg. su 28.
Scarica il documento per vederlo tutto.
Programmazione concorrente e distribuita Pag. 11
Anteprima di 7 pagg. su 28.
Scarica il documento per vederlo tutto.
Programmazione concorrente e distribuita Pag. 16
Anteprima di 7 pagg. su 28.
Scarica il documento per vederlo tutto.
Programmazione concorrente e distribuita Pag. 21
Anteprima di 7 pagg. su 28.
Scarica il documento per vederlo tutto.
Programmazione concorrente e distribuita Pag. 26
1 su 28
D/illustrazione/soddisfatti o rimborsati
Acquista con carta o PayPal
Scarica i documenti tutte le volte che vuoi
Dettagli
SSD
Scienze matematiche e informatiche INF/01 Informatica

I contenuti di questa pagina costituiscono rielaborazioni personali del Publisher biuzzo di informazioni apprese con la frequenza delle lezioni di Programmazione concorrente e distribuita e studio autonomo di eventuali libri di riferimento in preparazione dell'esame finale o della tesi. Non devono intendersi come materiale ufficiale dell'università Università degli Studi dell' Insubria o del prof Lavazza Luigi.
Appunti correlati Invia appunti e guadagna

Domande e risposte

Hai bisogno di aiuto?
Chiedi alla community