PRESENTAZIONE | SPECIFICHE
| BILANCIAMENTO DEL CARICO
| TOLLERANZA AI GUASTI |
ANALISI
DELLE PRESTAZIONI | METODO DI CALCOLO |
CONCLUSIONI
-
La comunicazione tra le entità presenti nel sistema avviene tramite
gli stream Java, utilizzando le socket con connessione per implementare
il concetto di stream. La scelta degli stream rispetto alla comunicazione
con datagrams è motivata dal fatto che in questo sistema la comunicazione
tra le entità avviene esclusivamente tramite lo scambio di oggetti,
che (soprattutto nel caso di trasmissione di immagini) possono avere dimensioni
considerevoli e necessitano di canale di comunicazione affidabile e che
mantenga l'ordine dei messaggi.
DFGClient:
-
L'invio di una richiesta di servizio consiste nella spedizione a DFGServer
di un oggetto della classe Messaggio_Mandelbrot
contenente i parametri necessari alla generazione dell'immagine.
-
I controlli sulla correttezza dei dati inseriti in una richiesta vengono
effettuati dal client (e vengono quindi trascurati sia dal server che dagli
slaves).
-
Dopo l'invio di una richiesta di servizio, il client rimane in attesa di
una risposta in modo sincrono. Viene però data la possibilità
all'utente di interrompere l'operazione in corso in qualunque momento.
-
Prima dell'invio di una richiesta di un calcolo e all'atto della ricezione
di una risposta (anche parziale) o al verificarsi di un errore, viene disabilitata
la funzione di invio di richieste di interruzione.
-
Dopo l'invio di una richiesta, non è possibile spedirne un'altra
fino alla ricezione di un risultato (anche parziale) o al verificarsi di
un errore.
-
Nel caso l'utente voglia interrompere il calcolo, viene spedito a DFGServer
un messaggio di tipo Messaggio_Interruzione.
Non si è ritenuto oppurtuno introdurre un time-out ma si è
scelto di lasciare all'utente la possibilità di interrompere il
calcolo di un'immagine per due motivi:
-
l'applicazione nel suo complesso è pensata per essere utilizzata
interattivamente, non per generare immagini in modalità batch.
-
la corretta scelta di un time-out è difficile e potrebbe contrastare
con i desideri dell'utente.
-
Nel caso l'operazione vada a buon fine, la risposta del server è
l'immagine richiesta, che viene visualizzata da DFGClient in un apposito
frame.
-
Nella versione definitiva di DFGClient, si è modificata la struttura
del codice in modo da poterlo eseguire sia come applet che come applicazione.
Quando è usato come applicazione, è necessario specificare
l'host di DFGServer sulla linea di comando: java dfg.dfg_client host_DFGServer
-
A causa delle restrizioni imposte dai security manager utilizzati dalle
varie versioni di browser e appletviewer provate, a volte l'applet non
è in grado di determinare il nome dell'host su cui sta eseguendo.
Per questo motivo, si utilizza un text field nel quale l'utente può
inserire il nome dell'host locale (ovviamente questo campo viene riempito
automaticamente quando DFGClient è utilizzato come applicazione).
D'altra parte, questa informazione è utilizzata solo per costruire
l'identificatore delle richieste inviate e ha il solo scopo di permetterne
il riconoscimento per una eventuale interruzione. Per questo motivo, si
può inserire una qualsiasi stringa che, assieme al numero di porta
utilizzato per inviare le richieste, costituisca un identificatore unico
nel sistema.
-
L'applet è a conoscenza dell'host su cui opera DFGServer in quanto
questo coincide con l'host da cui l'applet stesso è stato prelevato.
Nel caso l'applet venga caricato da hard disk anzichè tramite un
server web (questo accade sulle macchine lia, dove non è installato
un server web), questa informazione è però sconosciuta all'applet
e va quindi inserita dall'utente ; inoltre, in questa situazione il security
manager dell'applet permette solo di collegarsi a localhost, quindi l'applet
deve essere in esecuzione sullo stesso nodo sul quale è presente
DFGServer. Ovviamente, durante il normale funzionamento (cioè quando
l'applet viene scaricato da un server web oppure DFGClient è utilizzato
come applicazione), il text field viene riempito automaticamente.
-
L'applet è a conoscenza del numero di porta sul quale DFGServer
è in attesa di richieste di servizio (si è scelta la porta
20000).
DFGServer:
Per poter suddividere il calcolo di una immagine tra gli slaves, DFGServer
deve mantenere una struttura dati (chiamata Tabella) in cui memorizzare
gli slaves attualmente disponibili, la loro collocazione (nome dell'host
e numero di porta) e la loro situazione di carico. Vedere la sezione Bilanciamento
del carico per le specifiche relative alle modalità di scelta
degli slaves da utilizzare.
DFGServer deve essere in grado di servire vari tipi di richieste. Ogni
tipo di richiesta corrisponde ad un diverso tipo di Messaggio:
-
Messaggio_Mandelbrot: Viene utilizzato
dai DFGClient per richiedere la generazione di un'immagine. Il servizio
di questo tipo di richiesta viene svolto creando un oggetto GestoreRichiesta
che permette di effettuare la ripartizione del carico, ricevere le risposte
dai vari slaves e spedire la risposta al client.
-
Messaggio_Interruzione: Viene utilizzato
dai DFGClient per richiedere l'interruzione del calcolo di una immagine.
Il servizio di questo tipo di richiesta viene svolto creando un oggetto
GestoreInterruzione che permette di ritrovare gli slaves interessati dall'operazione
e inviare loro una richiesta di interruzione.
-
Messaggio_Registrazione: Viene utilizzato
dai DFGSlave per registrarsi nella Tabella.
-
Messaggio_Verifica: Viene utilizzato dai
DFGSlave per verificare periodicamente la presenza di DFGServer.
Operazioni svolte da DFGServer alla ricezione di un messaggio di tipo
Messaggio_Mandelbrot:
Bisogna innanzitutto osservare che ogni messaggio di questo tipo contiene
un elemento (ident) che lo identifica univocamente e il cui formato è
"host_DFGClient : porta_DFGClient". Questa informazione è utilizzata
solo per permettere il riconoscimento delle richieste di servizio per una
loro eventuale interruzione.
Se non ci sono slaves disponibili, DFGServer comunica al client che
il servizio non è attivo (chiudendo la connessione con esso). In
caso contrario, si procede come segue:
Per prima cosa viene creato un oggetto GestoreRichiesta, che viene
utilizzato per scandire la Tabella, crearne una copia contenente l'indicazione
del numero di righe che ciascuno slave deve calcolare, allocare un'area
di memoria per l'immagine, aggiornare la Tabella con la nuova situazione
di carico e creare, per ogni slave, un thread Esecutore che si occupi di
inviargli la richiesta e ricevere la corrispondente risposta.
I thread Esecutore vengono assegnati ad un gruppo di thread il cui
nome identifica univocamente il servizio che stanno svolgendo (viene usato
ident come nome per il gruppo) e viene dato loro un nome che permetta,
in caso di necessità (ved. la gestione
di una richiesta di interruzione), di ritrovare lo slave che devono
contattare (il formato del loro nome è: "host_DFGSlave : porta_DFGSlave").
La figura seguente mostra un possibile schema dei gruppi di thread e dei
thread in essi contenuti durante l'esecuzione di DFGServer:
Nel caso la richiesta vada a buon fine, ogni thread Esecutore ottiene la
porzione di immagine che gli era stata commissionata e la memorizza nella
posizione corretta dell'area di memoria appositamente predisposta dall'oggetto
GestoreRichiesta che lo ha generato. Quando tutti i thread Esecutore hanno
terminato il proprio compito, l'immagine ottenuta viene trasmessa al client.
Nota: il fatto di non realizzare GestoreRichiesta come thread (come
si era fatto nelle versioni preliminari di DFG) ma solamente come oggetto
di cui vengono invocati dei metodi introduce una serializzazione delle
operazioni svolte da DFGServer, il quale non si mette in attesa di nuovi
messaggi prima di aver creato tutti i thread Esecutore e aggiornato la
Tabella. Questo permette di risolvere a priori possibili situazioni di
corsa critica. Questa soluzione non provoca problemi di ritardo nel servizio,
visto che le operazioni svolte tramite GestoreRichiesta sono tutte operazioni
locali. Per lo stesso motivo, anche i thread GestoreInterruzione sono stati
modificati nello stesso modo.
Operazioni svolte da DFGServer
alla ricezione di un messaggio di tipo Messaggio_Interruzione:
In caso di interruzione volontaria del calcolo da parte dell'utente,
si potrebbe semplicemente lasciar continuare calcolo dell'immagine da parte
degli slaves, ignorandone la risposta. Questo approccio può però
provocare un sovraccarico del sistema e si ritiene quindi oppurtuno che
i DFGSlave interessati al calcolo dell'immagine vengano interrotti.
All'atto della ricezione di un Messaggio_Interruzione, viene creato
un oggetto GestoreInterruzione che permette di estrarre dal messaggio un
identificatore (ident) che corrisponde al lavoro da terminare e scandire
la lista dei gruppi di thread presenti nel gruppo di thread principale
(main), cercando il gruppo avente per nome ident. Questo gruppo contiene
i thread Esecutore relativi al lavoro da terminare. Viene quindi creato,
per ogni Esecutore trovato, un thread EsecutoreInterruzione che ha il compito
di spedire allo slave corrispondente un Messaggio_Interruzione. I thread
EsecutoreInterruzione vengono inseriti nel gruppo main.
Il procedimento di interruzione di un calcolo può essere realizzato
in modi diversi da quello scelto (ad esempio riducendosi semplicemente
a far terminare i thread Esecutore e GestoreMandelbrot coinvolti) . I pregi
del metodo scelto sono la semplicità dei protocolli di comunicazione
e la considerazione che la ricezione di un risultato (anche se parziale)
del calcolo da parte di DFGClient è importante.
Esempio di risultato parziale del calcolo |
|
-
Tutte le operazioni di modifica della Tabella da parte dei vari thread
devono avvenire in modo atomico (=>uso di metodi synchronized). L'esecuzione
concorrente dei metodi public contenuti nella classe Tabella può
infatti portare a stati inconsistenti della Tabella.
-
In caso di ricezione di un Messaggio_Interruzione relativo ad un job non
presente, non si fa nulla. Questa situazione può verificarsi se
il calcolo dell'immagine termina prima che il messaggio di interruzione
abbia il tempo di arrivare a DFGServer ed essere eseguito.
-
Se non si trova il gruppo di thread da interrompere (può accadere
se durante la spedizione e il trattamento del comando di interruzione il
calcolo termina), si prosegue l'esecuzione senza altre conseguenze.
-
Se l'aggiornamento della Tabella al termine di un
calcolo è in ritardo rispetto alla ripartizione di un nuovo lavoro,
questo viene suddiviso tra gli slaves utilizzando una situazione di carico
inesatta. Questo non può però essere considerato un errore
perchè, anche se il carico effettivo degli slaves non è quello
indicato in Tabella, il lavoro che stavano svolgendo non è ancora
terminato del tutto: manca l'aggiornamento della Tabella, che deve essere
considerata come l'ultima parte del lavoro.
-
E' possibile, in teoria, che una richiesta di interruzione
arrivi agli slaves interessati prima della corrispondente richiesta di
calcolo, risultando quindi inefficace. Questo può accadere se i
thread Esecutore sono molto più lenti a contattare gli slaves dei
corrispondenti thread EsecutoreInterruzione (nonostante questi siano stati
creati sicuramente dopo di essi). Anche per prevenire questa remota possibilità
si è previsto di poter inviare più messaggi di interruzione
durante il calcolo (vedere anche la sezione successiva).
Linea di comando: java dfg.dfg_server
DFGSlave:
-
All'avvio, ogni DFGSlave provvede a contattare DFGServer e a dichiarare
la propria disponibilità a ricevere richieste di servizio, indicando
un valore iniziale di carico che rappresenta il proprio carico di default.
Questo parametro deve poter essere specificato come argomento all'atto
dell'esecuzione del programma e non è previsto che possa essere
cambiato durante l'esecuzione (se verrà ritenuto necessario si potrà
aggiungere questa caratteristica). Il significato di questo parametro è
spiegato nella sezione Bilanciamento
del carico.
-
L'atto di registrazione presso DFGServer avviene spedendo un Messaggio_Registrazione
contenente le informazioni necessarie ad inizializzare una riga della Tabella.
-
Ad intervalli di tempo prefissati, DFGSlave si accerta che DFGServer sia
attivo spedendogli un Messaggio_Verifica.
-
Ogni DFGSlave presente nel sistema deve essere a conoscenza dell'host su
cui opera DFGServer e del numero di porta su cui esso è in attesa
di richieste.
DFGSlave deve essere in grado di servire due tipi di richieste. Ogni tipo
di richiesta corrisponde ad un diverso tipo di Messaggio:
-
Messaggio_Mandelbrot: Viene utilizzato
da DFGServer per richiedere il calcolo di immagini.
-
Messaggio_Interruzione: Viene utilizzato
da DFGServer per richiedere l'interruzione del calcolo relativo ad una
particolare immagine.
Operazioni svolte da DFGSlave alla ricezione di un messaggio di tipo
Messaggio_Mandelbrot:
Alla ricezione di una richiesta di questo tipo, DFGSlave crea un thread
GestoreMandelbrot che si occupa di servirla; il thread estrae dalla richiesta
i parametri, alloca un'area di memoria per l'immagine risultante e chiama
la funzione nativa per il calcolo dell'immagine.
Al thread viene assegnato come nome l'identificatore (ident) contenuto
nel messaggio stesso (nota: il formato di ident non ha importanza, basta
che permetta di identificare univocamente il lavoro che sta svolgendo).
Questo è necessario per poter servire una eventuale richiesta di
terminazione del calcolo proveniente da DFGServer. Il motivo della scelta
di avere slaves concorrenti è determinato da due fattori:
-
le risorse di calcolo di una macchina dotata di più CPU non verrebbero
sfruttate da una implementazione sequenziale degli slaves. (questa limitazione
potrebbe comunque essere superata eseguendo più istanze del programma
DFGSlave, ma sarebbe una soluzione meno elegante).
-
se gli slaves fossero sequenziali, nel caso DFGServer ricevesse un numero
di richieste di servizio superiore agli slaves disponibili, dovrebbe attendere
che almeno uno degli slaves si liberasse prima di poter iniziare a servire
tutte le richieste. L'implementazione parallela degli slaves permette invece
di poter sempre iniziare il servizio di una nuova richiesta (=>possibile
migliore throughput).
Operazioni svolte da DFGSlave alla ricezione di un messaggio di tipo
Messaggio_Interruzione:
DFGSlave estrae dal messaggio il parametro ident e scandisce la lista
dei thread contenuti nel gruppo main, uccidendo tutti i thread aventi per
nome ident (è possibile che ci sia più di un thread chiamato
ident se ci sono state ridistribuzioni di carico a seguito della caduta
di uno o più slaves). Prima di terminare, ciascuno dei thread interessati
spedisce il risultato parziale del calcolo a DFGServer. In questo modo,
DFGClient riceve un'immagine incompleta ma che può ugualmente essere
utile.
Nota: l'invio da parte di DFGClient di una richiesta
di interruzione può (in casi molto particolari) aumentare il tempo
di generazione di un'immagine. Ad esempio, se il messaggio di interruzione
arriva a DFGSlave quando il calcolo è già stato completato
ma prima (o durante) la spedizione a DFGServer del risultato, si interrompe
la connessione tra DFGSlave e DFGServer e quindi si provoca una ridistribuzione
di carico (e i nuovi slaves interessati non verranno interrotti). Per questo
motivo si è prevista la possibilità di poter inviare più
di una richiesta di interruzione da parte di DFGClient (anche se in pratica
queste situazioni non si sono mai verificate).
Linea di comando: java dfg.dfg_slave porta_slave host_server carico_di_default
PRESENTAZIONE | SPECIFICHE
| BILANCIAMENTO DEL CARICO
| TOLLERANZA AI GUASTI |
ANALISI
DELLE PRESTAZIONI| METODO DI CALCOLO | CONCLUSIONI