Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
vuoi
o PayPal
tutte le volte che vuoi
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: 1. Una risorsa deve essere condivisa tra due thread 2. Deve esistere almeno un percorso di esecuzione tra i thread in cui una risorsa è condivisa in modo non sicuro Per dimostrare la correttezza di un programma concorrente esistono due modi: - Dimostrare per induzione che il programma soddisfa tutte le condizioni sufficienti perché possa essere considerato sicuro (questo metodo spesso si dimostra troppo costoso) - Sviluppare il programma utilizzando strategie note chepermettono di evitare situazioni di race condition:- Fare in modo che la risorsa condivisa sia in uno stato sicuro prima di consentire ad un altro thread di accedervi
- Bloccare l'accesso alle risorse condivise mentre sono in uno stato non sicuro è alla base delle strategie per ottenere programmi concorrenti sicuri
- Acquire: acquisire l'accesso bloccando altri task ed eventualmente ponendo in attesa il task chiamante, se il semaforo è rosso
- Release: rilascia la risorsa ed eventualmente riporta ready un task in attesa
- Contatori: il semaforo...
- viene inizializzato a un valore intero positivo e a ogni richiesta viene decrementato (solo quando è zero il thread che fa una richiesta viene bloccato) e a ogni uscita viene incrementato (e se presente si sblocca un thread in attesa)
- Binari: il semaforo può essere solo zero o uno (rosso o verde)
- Generico n-ario: un mutex è un semaforo istanziato con valore 1
Synchronized
Ad ogni istanza della classe Object c'è un semaforo binario (indicato come lock), poiché ogni oggetto in Java estende la classe Object, ogni oggetto ha un suo lock.
La parola chiave synchronized applicata ad un metodo o ad una qualunque sezione di codice implica che:
- Quando si entra nel metodo/area synchronized si cerca di acquisire il lock associato all'oggetto
- Quando si esce dal metodo/area synchronized si rilascia il lock associato all'oggetto, quindi un eventuale thread in attesa può acquisire il lock ed entrare nel metodo/area
sull'oggetto prima di accedervi e successivamente rilasciare il lock intrinseco quando ha finito con loro.
- Tra il tempo di acquisizione e di rilascio del lock un thread ha un lock intrinseco.
- Nel tempo in cui un thread ha un lock intrinseco, nessun altro lock può avere lo stesso lock. Gli altri thread vengono quindi bloccati mentre aspettano di acquisire il lock.
Quando un thread invoca il metodo synchronized, assume automaticamente il lock intrinseco dall'oggetto del metodo.
Quando viene invocato un metodo statico il thread acquisisce il lock intrinseco dalla classe dell'oggetto associato con la classe.
Metodi e blocchi synchronized non assicurano l'accesso mutualmente esclusivo ai dati "statici" dato che sono condivisi da tutti gli oggetti della stessa classe.
Per accedere in modo sincronizzato ai dati statici si deve ottenere il lock su questo oggetto di tipo Class (in Java ogni classe è assegnato un oggetto di classe Class):
- Si
può dichiarare un metodo statico come synchronized.
Si può dichiarare un blocco come synchronized sull'oggetto di tipo Class. Bisogna però stare attenti al fatto che il lock a livello classe non si ottiene quando ci si sincronizza su un oggetto di tale classe e viceversa.
Si può quindi ereditare da una classe non sincronizzata e ridefinire un metodo come synchronized che richiama semplicemente l'implementazione della superclasse. Questo assicura che gli accessi ai metodi nella sottoclasse avvengano in modo sincronizzato.
Tramite la sincronizzazione un thread può modificare in modo sicuro dei valori che potranno essere letti da un altro thread, ma un thread non può sapere se i valori sono stati modificati. Per questo motivo oltre a synchronized vengono usati altri meccanismi (wait) che mettano in attesa un thread e che lo risveglino quando le condizioni sono cambiate.
Il metodo wait e il metodo notifyIl metodo wait viene utilizzato per
mandare in attesa il thread chiamante e per rilasciare il lock sull'oggetto. Un thread può chiamare il metodo wait
all'interno di un metodo sincronizzato, cioè quando detiene un lock.
La SVM (support vector machine) mantiene un elenco di tutti i thread che sono pronti per essere eseguiti ("Ready List"). Quando un thread va in wait la SVM lo sposta in una "Wait List" e rilascia il lock sull'oggetto. Quando un thread in attesa viene svegliato (notify
) la SVM lo sposta nella ready list. Programmazione Concorrente e Distribuita Page 8
Una chiamata notify
sposta un thread qualsiasi dal "wait set" di un oggetto al "ready set". Una chiamata a notifyAll
sposta tutti thread dal "wait set" di un oggetto al "ready set".
Monitor
Il monitor è una struttura dati con tutti i metodi synchronized
in modo che, in un dato momento, un solo thread può essere eseguito in qualsiasi metodo. In Java se un thread T1
Due ruoli dei thread:
- Chi aspetta il segnale
- Chi invia il segnale atteso
L'interfaccia dei thread è separata, in modo che ogni thread può chiamare l'operazione più appropriata. Creiamo due interfacce con metodi distinti, e una classe astratta che li implementa, dove richiamerà i metodi definiti.
Buffer:
- Contiene dati che dopo essere stati letti verranno distrutti
Blackboard:
- Simile al buffer ma:
- Ha una lettura non distruttiva dei dati
- Messaggi lasciati sulla blackboard sono preservati fino a che non vengono cancellati, sovrascritti o invalidati
- La scrittura non è bloccante
Operazioni su blackboard:
- Messaggi scritti --> write()
- Messaggi cancellati --> clear()
- Blocco canale fino a che i dati rimangono non validi --> read()
- Se ci sono dati disponibili --> dataAvailable()