Modelli Ci ò - unibo.itL'ereditariet àè un ottima scelta dal punto di vista della modellazione....

28
Modelli Ciò che ci importa nei sistemi distribuiti e tutto ciò che ha impatto durante l'esecuzione e che rimane significativo e vitale durante questa favorendo ed abilitando la distribuzione. 9 Nel distribuito si privilegia sempre l'uso di modelli dinamici, ma ciò rende molto difficile la gestione. Esempio: Supponiamo un sistema composto da molti servizi, come fanno i clienti a conoscere i servitori? In questo contesto avremmo bisogno di una serie di gestori (ad esempio gestori di nomi) in grado di dirci chi sono e dove trovarli. Ragionando quindi in maniera ingegneristica, quando si aggiunge una certa caratteristica, quello di cui bisogna tener conto è quanto costa la caratteristica stessa. 10 Monoutente: le decisioni dell'unico utente presente si concentrano su un unica risorsa. Multiutente: le decisioni di più utenti di distribuiscono in maniera bilanciata utilizzando tutte le risorse in modo corretto. Questo modo di lavorare è particolarmente adatto per i sistemi di gestione in cui bisogna tener sotto controllo più risorse, introducendo problemi di sicurezza. Quando si parla di come si usano i processori, si può lavorare in due modi: modello workstation: ossia quando si lavora alla propria macchina personale. In generale, quando si lavora solo su risorse di una particolare macchina, il che non evita di poter spostarsi su altre macchine continuando quindi a lavorare unicamente su risorse allocate su tale macchina. Processor pool: è un sistema composto da una serie di processori i quali possono essere usati da chiunque, quindi possono essere acceduti da qualsiasi postazione e macchina. Ciò che succede in tale modello è che le risorse, alle quali come già detto possono accedere chiunque da postazioni differenti, sono messe a disposizione da utenti diverse situati su macchine diverse. Per tale motivo tale modello è decisamente più idoneo per il distribuito. Per ciò, la gestione di un modello a processor pool è più complicata e difficoltosa. 11 I processi, accedono e rendono disponibile l'accesso, a delle risorse che possono essere: private: del processo stesso, quindi trattate solo da quest'ultimo con inevitabile assenza di colli di bottiglia nel loro utilizzo (ad esempio sue variabili) perché si sta facendo riferimento ad azioni per l'appunto locali. Condivise: con le quali bisogna fare attenzione per la possibile presenza di interferenze dovute al loro utilizzo da parte di altri utenti. In questo caso avvengono azioni di comunicazione con altri processi attraverso memoria condivisa e scambipiè di pagina pari dispari diverso di messaggi; si usano dati esterni ai processi stessi. Reti di Calcolatori LM 1

Transcript of Modelli Ci ò - unibo.itL'ereditariet àè un ottima scelta dal punto di vista della modellazione....

Page 1: Modelli Ci ò - unibo.itL'ereditariet àè un ottima scelta dal punto di vista della modellazione. Se volessimo, ad esempio, data una classe ed una sua super classe, avere un ulteriore

Modelli

Ciò che ci importa nei sistemi distribuiti e tutto ciò che ha impatto durante l'esecuzione e che

rimane significativo e vitale durante questa favorendo ed abilitando la distribuzione.

9

Nel distribuito si privilegia sempre l'uso di modelli dinamici, ma ciò rende molto difficile la

gestione.

Esempio:

Supponiamo un sistema composto da molti servizi, come fanno i clienti a conoscere i servitori?

In questo contesto avremmo bisogno di una serie di gestori (ad esempio gestori di nomi) in grado di

dirci chi sono e dove trovarli.

Ragionando quindi in maniera ingegneristica, quando si aggiunge una certa caratteristica, quello di

cui bisogna tener conto è quanto costa la caratteristica stessa.

10

Monoutente: le decisioni dell'unico utente presente si concentrano su un unica risorsa.

Multiutente: le decisioni di più utenti di distribuiscono in maniera bilanciata utilizzando tutte

le risorse in modo corretto. Questo modo di lavorare è particolarmente adatto per i sistemi di

gestione in cui bisogna tener sotto controllo più risorse, introducendo problemi di sicurezza.

Quando si parla di come si usano i processori, si può lavorare in due modi:

modello workstation: ossia quando si lavora alla propria macchina personale. In generale,

quando si lavora solo su risorse di una particolare macchina, il che non evita di poter spostarsi su

altre macchine continuando quindi a lavorare unicamente su risorse allocate su tale macchina.

Processor pool: è un sistema composto da una serie di processori i quali possono essere

usati da chiunque, quindi possono essere acceduti da qualsiasi postazione e macchina. Ciò che

succede in tale modello è che le risorse, alle quali come già detto possono accedere chiunque da

postazioni differenti, sono messe a disposizione da utenti diverse situati su macchine diverse.

Per tale motivo tale modello è decisamente più idoneo per il distribuito.

Per ciò, la gestione di un modello a processor pool è più complicata e difficoltosa.

11

I processi, accedono e rendono disponibile l'accesso, a delle risorse che possono essere:

private: del processo stesso, quindi trattate solo da quest'ultimo con inevitabile assenza di

colli di bottiglia nel loro utilizzo (ad esempio sue variabili) perché si sta facendo riferimento ad

azioni per l'appunto locali.

Condivise: con le quali bisogna fare attenzione per la possibile presenza di interferenze

dovute al loro utilizzo da parte di altri utenti. In questo caso avvengono azioni di comunicazione con

altri processi attraverso memoria condivisa e scambipiè di pagina pari dispari diverso di messaggi;

si usano dati esterni ai processi stessi.

Reti di Calcolatori LM 1

Page 2: Modelli Ci ò - unibo.itL'ereditariet àè un ottima scelta dal punto di vista della modellazione. Se volessimo, ad esempio, data una classe ed una sua super classe, avere un ulteriore

Non si può parlare di processi senza parlare di oggetti: contenitori di informazioni.

Un OGGETTO è qualche cosa, tipicamente, che ha il suo contenuto non accessibile a tutti perché

presenta vincoli d'accesso. Tale contenuto quindi non è accessibile direttamente dall'esterno

(principio di astrazione) se non con metodi che permettono di accedere alle risorse dell'oggetto

stesso.

Gli oggetti si distinguono in oggetti che eseguono (oggetti attivi) ed oggetti invece, che hanno solo

del contenuto e che aspettano di essere acceduti (oggetti passivi); per tale motivo, potrebbero

avvenire problemi di interferenza di processi che accedono agli oggetti. Pertanto un oggetto deve

avere conoscenza di come viene acceduto!

<<Come può un oggetto sapere chi e come questo chi l'ha acceduto?>>

A tal proposito, vi sono modelli in cui un oggetto permette di essere acceduto se e solo se é lui

stesso a “darne il permesso”.

12

Il modello ad oggetti passivo è di Java.

É un modello in cui gli oggetti, contenitori d' informazioni, aspettano di essere acceduti, il che può

portare ad una situazione in cui più oggetti esterni accedano ad un unico oggetto cambiandone lo

stato (nella slide le frecce rappresentano per l'appunto l'accesso all'oggetto).

Tale modello non è quindi un modello molto astratto dal punto di vista dell'esecuzione proprio

perché quando ne scrivo il codice, devo sapere se tale oggetto può essere acceduto da uno o più

oggetti ed in tal caso se lo possono fare contemporaneamente: modello a scarso confinamento.

Nel modello ad oggetti attivi , in linea di principio, è l'oggetto stesso che decide chi far entrare e

quando farlo entrare.

13 - 14

In generale, un oggetto è composto da metodi e dallo stato e per stato si intendono le variabili

dell'oggetto, ossia il contenuto, al quale ci si può accedere invocando un metodo dell'oggetto.

Se l'oggetto è

Passivo:

un processo arriva, ne richiede il metodo e modifica le variabili. Il tutto avviene in maniera

incontrollata.

Attivo:

tutta la parte di visibilità ed esecuzione del metodo, non è più visibile all'esterno.

In quest'ultimo caso ci sarà quindi tutta una parte di supporto per la gestione dell'accesso all'oggetto

e delle richieste degli oggetti che arrivano dall'esterno. Tale supporto funge da filtro e decide chi far

passare e chi no: avremo quindi una coda delle richieste (politica FIFO: dipenderà dal contenuto, ad

esempio se qualità differenziata o no...). Così facendo la visibilità dall'esterno viene spezzata, e la

politica di scheduling viene decisa all'interno dell'oggetto che viene quindi conseguentemente

protetto da interferenze esterne.

Nel distribuito avere degli oggetti ATTIVI è particolarmente ottimo!

15

Ragionando ora sui dati, questi si distinguono in:

2 Stefano Di Monte

Page 3: Modelli Ci ò - unibo.itL'ereditariet àè un ottima scelta dal punto di vista della modellazione. Se volessimo, ad esempio, data una classe ed una sua super classe, avere un ulteriore

• primitivi: insieme di valori

• riferimenti ad altri oggetti: che a loro volta possono essere:

� per riferimento: non sono dei valori ma puntatori ad altri oggetti che sono quindi riferiti

da questo.

� Per contenimento: ossia che un oggetto è realmente dentro un altro oggetto (legame

molto forte). (I primi middleware lavoravano in questo modo.)

17

Supponiamo di avere delle istanze con caratteristiche comuni e che quindi hanno lo stesso

comportamento, quest possono conseguentemente esser messe a fattor comune: vi è quindi una

classe, un contenitore descrittivo, che dice quali sono i valori delle variabile delle istanze che a loro

volta rappresentano i metodi i quali possono essere invocati su ciascuno di questi valori dalle

singole istanze.

CLASSE= contenitore descrittivo di una serie di istanze.

<<Vi sono classi a run-time?>> Si! (ad esempio in Java sono caricate dinamicamente, quindi su

bisogno.)

<<Dove?>> La JVM tende a caricare le classi nell' heap.

<<Quando?>> Quando un'istanza di quella classe dev'essere caricata (esigenza vera). Di

conseguenza, per creare l'oggetto O1 della classe C1, dovremmo prima caricare la classe C1.

Quando poi vengono scaricate, ossia quando non vi è più necessità (o anche no!), viene liberata

memoria che sarà usata quindi, per caricare altre classi.

18

Come per molti altri linguaggi ad oggetti, si prevede un'ereditarietà:

data una serie di istanze, i valori di queste istanze sicuramente sono specificate nella classe C, ma

potrebbero essere specificate anche in una super classe SC: si forma così una catena con una classe

e possibilmente molte (ereditarietà multipla) super classi. (NOTA: in Java vi sono solo catene ad

ereditarietà semplice ossia con una sola super classe).

L'ereditarietà multipla comporta certe problematiche (come ad esempio la necessità di specificare il

percorso esatto dell'albero di ereditarietà da dover percorrere per accedere alle istanze di una certa

classe), problematiche che hanno un certo impatto sull'esecuzione, sicuramente in maniera negativa.

Difatti, il problema di quale percorso percorrere su di un certo albero di ereditarietà, nel distribuito

non è un problema da poco perché le classi potrebbero (e sicuramente lo sono) essere sparate su

nodi diversi, comportando la presenza di tempi molto forti di ritardo.

Per tale motivo le classi vengono tenute, laddove è possibile, tutte insieme, compattate (assieme alle

super classi) in un unico contenitore che sarà quindi la descrizione di tutte le sue caratteristiche.

20 - 21 - 22

Pertanto l'ereditarietà introduce problemi di gestione!

<<Concludendo, quale delle due ereditarietà, tra quella semplice e quella multipla, è migliore?>>

L'ereditarietà è un ottima scelta dal punto di vista della modellazione. Se volessimo, ad esempio,

data una classe ed una sua super classe, avere un ulteriore comportamento (del sistema), in

ereditarietà semplice saremmo costretti a riscrivere il tutto mente il ereditarietà multipla no.

Reti di Calcolatori LM 3

Page 4: Modelli Ci ò - unibo.itL'ereditariet àè un ottima scelta dal punto di vista della modellazione. Se volessimo, ad esempio, data una classe ed una sua super classe, avere un ulteriore

D'altro canto, l' overhead di gestione introdotto dall'ereditarietà multipla ha favorito, da questo punto

di vista, lo sviluppo dell'ereditarietà semplice.

Se ci si basa sul grado di accettazione dell'utente finale (che è ciò che conta in termini di distanza

temporale), gli strumenti semplici sono (quasi) sempre i migliori!

23

Quando lavoriamo nel distribuito, lavoriamo sempre con dei componenti: classi, oggetti ed

interfacce. Quest'ultima è la specifica di un contratto, non potrebbe avere un'implementazione a

differenza delle classi che invece rappresentano qualcosa di più concreto.

L'interfaccia quindi, rappresenta solo un contratto, la descrizione/firma dei metodi con i quali si può

accedere all'interno del contenitore/oggetto.

In generale, nel distribuito, quando si parla di un componente (ad esempio della classe C) e

quest'ultima ha 4 super classi in ereditarietà (possibilmente anche in catena) molto spesso i metodi

(organizzati tipicamente su una altro nodo) vengono raggruppati tutti insieme (come se avessi un

unico contenitore con i metodi della classe e delle super classi) per facilitarne la navigazione. Tale

contenitore potrebbe essere condiviso poi da tutte le istanze.

Ciò facilita un possibile spostamento (!!) del contenitore stesso, essendo quest'ultimo molto

compattato.

C'è però un problema:

Nell'invocare un qualche metodo di un certo componente, devo sapere come riferirlo e, non

potendoci essere una gestione semplice, le cose si complicano leggermente.

Ad esempio, potrebbe succedere che in due contenitori diversi ci siano gli stessi metodi (ad esempio

della classe desc1) ma in posizioni diverse: spiazzamento dipendente dal linguaggio,

dall'ereditarietà utilizzata....

<<Come posso, quindi trovare i metodi che m'interessano?>>

24

In generale, quando si compatta tutto in un contenitore (per i sistemi che lo fanno), si decide che

l'accesso all'oggetto avvenga attraverso una tabella unica in cui vi sono i puntatori ai metodi

necessari!

Ogni singolo componente ha quindi, dei puntatori ai suoi metodi necessari!

Così facendo, si risolve il problema della posizione, in quanto, se volessi richiedere un metodo della

classe 1 andrei ad uno spiazzamento di +1 rispetto alla posizione 0 in cui ci sono i metodi della

classe corrente.

Attenzione:

“Spiazzo” che però potrebbe essere DIFFERENTE per componenti diversi!

25

Tutti i sistemi che lavorano in dinamico, per poter ritrovare le cose di interesse, mettono a

disposizione delle API (in questa slide chiamata in modo astratto) che consentono, per l'appunto, di

poter ritrovare ciò che si vuole. Tali funzioni vengono invocate dal supporto per ottenere la

focalizzazione e la posizione dei metodi di interesse.

26

Questa funzione è nascosta dal supporto, non visibile dall'utilizzatore finale, anche se in alcuni casi

bisogna che ci sporchiamo le mani, bisogna quindi conoscerla, come succede in DCOM.

4 Stefano Di Monte

Page 5: Modelli Ci ò - unibo.itL'ereditariet àè un ottima scelta dal punto di vista della modellazione. Se volessimo, ad esempio, data una classe ed una sua super classe, avere un ulteriore

Nell'ambito di DCOM vi è una funzionalità, la QueryInterface, che è parte dell'interfaccia di base di

ogni componente.

27

semantica per riferimento: gli oggetti non contengono altri oggetti, bensì riferimenti ad altri oggetti;

si creano quindi delle catene tra gli oggetti stessi

28-->32

Ci potrebbero quindi essere riferimenti reciproci, tra gli oggetti; più precisamente, ciò che si intende

dire è che un oggetto, sicuramente ha sue variabili (ad esempio primitive), ma è possibile che ne

veda degli altri perché ha un riferimento affinché gli altri lo cerchino.

Supponendo di voler fare una macchina virtuale, quello che si potrebbe pensare è di voler far sì che

tale riferimento sia locale (abbreviato ad esempio in un indirizzo di memoria), ovviamente in questo

scenario c'è un problema quando si porta tutto nel distribuito perché la semantica è locale e non la

posso usare intra-nodo. C'è una sostanziale disomogeneità tra quello che vedo localmente e quello

che vedo non localmente.

<<In java i riferimenti locali sono statici?>>

In generale non sono risolti staticamente bensì dinamicamente.

Come prima soluzione è stato pensato di inserire una specie di via di fuga, ossia di comunicare con

gli strati di comunicazione, attraverso ad esempio delle socket, portando alla creazione di oggetti

ottenuti come risultato di una ricezione , aumentando il dominio della visibilità locale (perché creo

delle cose nuove). Tale modo di lavorare è significativo perché nel mio dominio aggiungo delle cose

nuove ma d'altronde si complica la gestione del tutto perché, naturalmente, bisogna sapere con chi

stiamo comunicando, di conseguenza c'è bisogno di un sistema di nomi.

Come si nota, già a questo livello abbiamo bisogno dell'aiuto di un qualche supporto.

Questo primo modo di lavorare è però insufficiente per ciò che vorremmo noi avere, ossia riuscire,

in modo più trasparente all'utente, a vedere delle entità locali e (circa allo stesso modo) remote:

vogliamo avere dei riferimenti remoti (fondamentali nel distribuito).

Un riferimento remoto lo si implementa, ad esempio, attraverso il principio di delega: l'utente crede

che stia riferendo direttamente un' entità remota, ma in realtà vi è un intermediario locale (proxy)

che si preoccupa di passare le cose dall'altra parte; non si parla di trasporto di qualcosa dall'altra

parte bensì di capacità di portare delle cose dall'altra parte.

Normalmente questo schema prevede due proxy, uno lato cliente ed uno lato servitore, che

coordinano il lavoro e permettono il riferimento remoto.

Quindi ci dev'essere qualcuno che si preoccupa di creare i proxy i quali lavorano durante

l'esecuzione.

Così facendo abbiamo bisogno di fare delle previsioni molto precise di con chi si comunicherà in

remoto!

Ragionando sui tempi di vita, in generale lavorando in locale, quando ho un riferimento per un'altra

entità, tipicamente questa entità deve esistere, e se per caso qualcuno la distrugge sappiamo con

certezza che a livello locale tipicamente questo non succede: se per assurdo succedesse, non avrei il

riferimento giacché passerebbe il garbage collector e lo eliminerebbe; in remoto, invece, il

riferimento remoto è più lasco di quello locale, ad esempio potrei aver preparato la struttura di

supporto ma potrei non avere il servitore dall'altra parte magari per mancanza di coordinamento tra i

due nodi.

Reti di Calcolatori LM 5

Page 6: Modelli Ci ò - unibo.itL'ereditariet àè un ottima scelta dal punto di vista della modellazione. Se volessimo, ad esempio, data una classe ed una sua super classe, avere un ulteriore

Vi sono comunque, altri modelli da prendere in considerazione, ad esempio:

<<il proxy C1 per un istanza C1, potrebbe essere usato per un altra istanza dello stesso tipo C2 sullo

stesso nodo N1? In altre parole, un proxy potrebbe servire più richieste di quel nodo verso un altro

nodo?>>

Certo!

Però potrebbe anche essere che sia necessario avere un proxy per ogni possibile riferimento, con

possibile conseguente esplosione del numero dei proxy...aumentando la difficoltà di gestione del

tutto. Tutto dipende quindi dal contesto.

<<Cosa succede in RMI se qualcuno ha spento il servitore?>>

Abbiamo un eccezione!

Ciò non ci piace, di conseguenza ciò che vogliamo è che se facciamo una richiesta ad un server

remoto, questo debba essere acceso e funzionante!

Sicuramente quando lavoriamo con riferimenti remoti abbiamo bisogno di un sistema di nomi, i

quali possono essere molto vari ma che comunque svolgono un ruolo essenziale.

Supponendo di aver fatto un riferimento remoto in RMI e per tale riferimento supponiamo di aver

fatto un metodo con il quale ovviamente passiamo delle informazioni all'altro, ad esempio dati

primitivi o delle istanze di certe classi (contenuto un po' più organizzato), potrebbe essere che

dall'altra parte tale classe si conosca, che sia quindi la stessa uguale classe, ma non c'è alcuna

garanzia che sia perfettamente uguale (ci potrebbe essere un metodo in più o in meno o delle

variabili che da una parte ci sono e dall'altra parte no!), al fine di sapere se le classi sono identiche

una prima soluzione che viene in mente è fare del matching tra le classi (fare un hash della classe

prima di passarla e poi controllare l'hash a passaggio avvenuto dall'altra parte). Ma questa non è

l'unica soluzione. Vi sono soluzioni più complicate che rendono la durata dell'applicazione

superiore.

Parlando di modelli, di middleware, bisogna sempre pensare a cosa c'è sotto, ossia che i riferimenti

remoti richiedono un supporto al di sotto, senza il quale non possiamo lavorare, un supporto che

prenda delle decisioni, che preveda dei costi (più o meno alti) ma che permetta il buon

funzionamento del tutto, garantendo la mancanza di errori.

33

Supponendo, ora, di avere un applicazione, magari scritta in un unico linguaggio (ciò è molto

limitativo: difatti quasi mai realmente vero).

Ciò che ci aspettiamo è che, dopo il deployment effettuato perché abbiam necessità e volontà di

eseguire, il programma sviluppato possa essere adatto per diversi deployment e non per uno solo,

questo perché l'intera applicazione, spesso e volentieri, non ci sta su un solo nodo ma è divisa su

nodi diversi (con deployment diversi). Ciò che vogliamo, in sintesi, è poter cambiare la

configurazione dell'applicazione senza cambiare le singole sottoparti.

Supponendo ora di avere C1 e C2 su di uno stesso nodo e con riferimenti reciproci, è ovvio che

questi possono essere intesi come riferimenti locali, cosa che non sarebbe vera se fossero su nodi

diversi (riferimenti remoti in questo caso), devo quindi sapere come sono distribuiti i componenti

affinché possa aver preparato i relativi proxy per la comunicazione comportando un relativo costo

aggiuntivo.

Dover mettere dei componenti che interagiscono molto su uno stesso nodo, potrebbe essere una

decisione sbagliata perché, così facendo, i tempi di esecuzione dei due processi sarebbero in mutua

6 Stefano Di Monte

Page 7: Modelli Ci ò - unibo.itL'ereditariet àè un ottima scelta dal punto di vista della modellazione. Se volessimo, ad esempio, data una classe ed una sua super classe, avere un ulteriore

esclusione e non sovrapposti come se fossero su due nodi diversi (impatto sui tempi di esecuzione

finali), d'altro canto, dato che la comunicazione tra i componenti si svolge attraverso una banda, ed

essendo questa infinita se i due processi sono su di uno stesso nodo potrebbe essere un ottima

soluzione, ad esempio, per due processi che comunicano poco ma spesso (?!) (su due nodi diversi la

banda è limitata).

34

Quando progettiamo un'applicazione, di solito questa è composta da entità statiche (create all'inizio

e che durano durante tutta l'applicazione -ad esempio i demoni, le variabili...-) e da risorse

dinamiche, attivate quindi su bisogno, e la cui creazione non è semplice da prevedere, e ciò aumenta

di conseguenza la complessità dell'applicazione stessa.

35

Quindi sulle risorse statiche, possiamo prendere delle decisioni anche costose perché avvengono

prima dell'esecuzione e soprattutto, non hanno impatto sull'esecuzione stessa, tuttavia ci sono delle

situazione in cui l'allocazione non può essere decisa in modo statico, perché, come abbiam detto,

tali situazioni si manifestano durante l'esecuzione! In realtà ciò non è sempre vero, in quanto posso

prendere delle decisioni prima dell'esecuzione, quindi in modo statico, circa risorse che saranno poi

create dinamicamente durante l'esecuzione. (Ad esempio nella JVM molto spesso le decisioni

vengono prese prima dell'esecuzione). Ciò potrebbe comunque essere una politica sbagliata nel caso

in cui ad esempio, C1 di N1 crei sempre su N2 ingolfandolo conseguentemente. La soluzione quindi

sarebbe quella di adottare politiche dinamiche con conseguente aumento dei costi. Esistono altresì

politiche ibride.

Ne deriva che, la politica statica è molto semplice ma anche molto rigida, al contrario di quella

dinamica che invece è molto più flessibile.

36

Come facciamo in soldoni a fare le cose?

Nella maggior parte dei sistemi, a mano, quindi staticamente, si decide come e dove dev'essere

messo un certo componente e ciò per tutti i componenti; tale modo di lavorare è molto pesante

perché per ogni singola risorsa bisogna decidere dove dev'essere messa.

La prima cosa che viene in mente, quindi, è quella di utilizzare linguaggi di script per eseguire il

deployment.

Vi son altresì approcci basati su modelli dell' applicazioni, attraverso i quali specificare i vincoli

necessari fra le diverse parti dell'applicazione stessa, tutto ciò avviene attraverso degli strumenti di

supporto.

37-38

Modelli di allocazione:

Approccio esplicito= comandato dall'utente che specifica il mappaggio dell'applicazione. Approccio

granulare ma che richiede un costo molto elevato dal punto di vista dell'utente stesso.

Approccio implicito= approccio automatico, l'utente non si occupa del deployment perché vi è

qualcun' altro che lo fa al posto suo. Quindi tale approccio prevede che ogni singola entità creata in

un programma trovi, grazie ad un supporto, la propria allocazione. Tale approccio dev'essere quindi

valido sia per le cose che succedono durante l'esecuzione sia per quelle cose che succedono prima

dell'esecuzione.

Reti di Calcolatori LM 7

Page 8: Modelli Ci ò - unibo.itL'ereditariet àè un ottima scelta dal punto di vista della modellazione. Se volessimo, ad esempio, data una classe ed una sua super classe, avere un ulteriore

Tali approcci sono agli estremi, di mezzo c'è l'approccio misto= alcune delle risorse statiche e/o

dinamiche vengono guidate dall'utente, tutto il resto viene gestito in modo implicito. Ciò semplifica

la vita all'utente. Tale sistema, in generale è il migliore proprio perché vi è un supporto al di sotto ,

che per la maggior parte delle cose (come ad esempio il bilanciamento delle risorse) fa delle

valutazioni e poi vi sono delle indicazioni dell'utente tenute in conto per migliorare le prestazioni

39

Il deployment è quindi un problema non semplice da affrontare, ma che incide molto sulle

prestazioni del sistema.

Ad esempio, per due componenti che debbano comunicare e che siano su di uno stesso nodo (locali)

possiamo quindi condividere la memoria ed anche dei file, delle socket, insomma tutta la parte del

sistema operativo. Tutto ciò non si può fare se siamo in remoto.

Pertanto, in locale non utilizzerò di sicuro RMI, come in remoto non utilizzerò sicuramente dei

riferimenti locali!

40-41

Il modello cliente/servitore è il modello più usato, nasconde tutta una serie di dettagli

implementativi che sono curati dal supporto.

Il modello c/s è un modello a forte accoppiamento, in quanto prevede che il cliente conosca il

servitore; inoltre prevede, di default, una comunicazione sincrona (che c'è il risultato) bloccante (che

c'è attesa per l'arrivo del risultato).

Una volta scelto il modello da utilizzare, succede sempre che vengono eseguite delle modifiche,

questo perché non sempre il modello scelto riesce ad incarnare tutte le situazioni possibili che

vengono in mente.

Alcuni modifiche sono:

Modello pull

Come è ovvio, è possibile che un cliente possa prendere l'iniziativa, pertanto in questo caso il cliente

stesso è tenuto a richiedere il servizio ad un servitore, quindi recuperare la risposta, se prevista.

Modello push

parte della responsabilità della risposta potrebbe essere fornita dal provider del servizio; è un

modello in cui il cliente non aspetta la risposta (dopo aver fatto richiesta è libero di fare ciò che

vuole), perché è compito del servitore di fornire la risposta che poi verrà recuperata localmente dal

cliente. Scambio ASINCRONO d'informazione. Da notare che il servitore può essere sincrono

bloccante o anch'esso asincrono.

Modello A delega

Potrebbe essere che il cliente non voglia aspettare il risultato, quindi, complicando un po' il tutto,

l'interazione prevede una terza parte, ossia qualcuno che possa aspettare il risultato, per l'appunto il

delegato. Il cliente, a questo punto recupererà il risultato da quest'ultimo, e potrebbe farlo anche

continuamente.

Modello a notifica

Stesso principio della delega, ma in questo caso il delegato con un messaggio di notifica, avvisa il

cliente dell'arrivo della risposta.

8 Stefano Di Monte

Page 9: Modelli Ci ò - unibo.itL'ereditariet àè un ottima scelta dal punto di vista della modellazione. Se volessimo, ad esempio, data una classe ed una sua super classe, avere un ulteriore

Il cliente è disaccoppiato in maniera forte dal risultato che verrà gestito dal proxy sottostante.

42

Lavorando sincrono (c'è il risultato) non-bloccante (non ci blocchiamo nell'attesa del risultato) è

possibile lavorare, come si dice, per delega, differenziando il caso in cui si lavora con il:

poll-object

il mittente chiede un operazione al servitore indicandogli un oggetto (il poll-object) destinato a

contenere il risultato. E' compito di tale oggetto di contenere il risultato destinato al cliente

originario; ci sarà quindi un qualche meccanismo (modello di notifica) che consente al cliente

originario di andare a prendere il risultato. Si dice che sitam lavorando poll per il recupero del

risultato.

Abbiamo quindi una interazione tra il poll-object e il cliente originario necessaria, senza la quale

non sarebbe possibile far arrivare il risultato al cliente stesso.

Call-back-object

L' oggetto destinatario del risultato è molto indipendente dal cliente originario ed è responsabile nel

trattare in maniera concreta il risultato proveniente dal servitore. Non c'è nessuna interazione, a

livello di principio, push-poll tra il cliente originario e il call-bak object il quale può trattare il

risultato in maniera opportuna; maniera dettata comunque dal cliente prima di richiedere il servizio

suddetto.

Queste due forme sono entrambe molto usate. Guardando i modelli, si vede però che una delle due

forme è fortemente disaccoppiata dalla vita del cliente, naturalmente stiam parlando del call-back

object che potrebbe essere “ancora in vita” anche dopo che il cliente abbia deciso di andare via, a

differenza dell poll-object in cui vi è compresenza tra delegato e cliente.

Naturalmente, per entrambe le forme, l'interazione globale è sincrona non bloccante.

<<Cosa succede se un cliente fa più richieste usando lo stesso poll-object?>>

Bisogna vedere come è fatto il supporto, quindi l'infrastruttura, che potrebbe prevedere una coda,

oppure anche un solo spazio che verrà quindi continuamente sovrascritto dall'ultima richiesta

arrivata.

<<e che garanzie abbiamo che quando andiamo a richiedere il risultato sia proprio quello nostro?>>

Una coda non è sufficiente, bisognerebbe etichettare le richieste. Per questo la maggior parte dei

sistemi che utilizza poll-object lavora con un unico spazio.

Lavorando invece con call-back siamo noi (clienti) che decidiamo come trattare le molteplici

risposte.

43

Reti di Calcolatori LM 9

Page 10: Modelli Ci ò - unibo.itL'ereditariet àè un ottima scelta dal punto di vista della modellazione. Se volessimo, ad esempio, data una classe ed una sua super classe, avere un ulteriore

Prima di fare modelli cliente-servitore sono stati realizzati modelli a scambio di messaggi. Questi,

sono strumenti molto flessibili di basso livello e ce ne sono di moltissimi tipi (socket, …).

A differenza del modello (un) cliente (ed un) servitore, qui ci potrebbero essere più destinatari ed un

solo mittente (broadcast, il messaggio arriva a tutti) oppure gruppi di destinatari (multicast, invio

messaggi differenziato per gruppi di destinatari).

In questo modello potrebbero esserci problemi di semantica: il cliente vuole una certa risposta e

magari gliene arriva un'altra fatta in maniera differente.

Tali modelli, sono stati creati per lavorare in modo ASINCRONO , quindi senza qualità: io inoltro

una richiesta e spero che venga ricevuta. Nonostante ciò, in alcuni sistemi si possono trovare

modelli a scambio di messaggi con interazione sincrona: io mando un messaggio e m 'aspetto un

ack che mi dia, quindi, conferma dell'avvenuta ricevuta del messaggio.

Specifiche semantiche:

Asimmetrico: il mittente conosce il destinatario ma non viceversa.

Simmetrico: il mittente conosce il destinatario e viceversa. Addirittura, ci son dei sistemi in cui il

cliente deve specificare a chi vuole inviare il messaggio e dall'altra parte il destinatario deve

specificare da chi vuole essere inviato dei messaggio. Quest'ultimi sono schemi fortemente

simmetrici, mai affermati in maniera forte perché comporterebbe una completa conoscenza di chi si

vuole che invii dei messaggi e a chi si devono inviare, ciò è estremamente vincolante.

Entità intermedie: con le socket stiamo lavorando in maniera indiretta, anche se sono oggetti locali

abbiam comunque un protocollo che ci permette di trasportare cose dall'altra parte. Nei modelli a

blocco diretto ci son veri e propri oggetti delegati nel ricevere la risposta.

Comunicazione vs Sincronizzazione: nel primo vi è scambio di informazioni, con il secondo invece

si stabiliscono le regole per il trasferimento: non vi è quindi scambio di contenuto.

Possibilità realizzative:

bloccante/non bloccante: aspetto/ non aspetto la risposta del server.

10 Stefano Di Monte

Page 11: Modelli Ci ò - unibo.itL'ereditariet àè un ottima scelta dal punto di vista della modellazione. Se volessimo, ad esempio, data una classe ed una sua super classe, avere un ulteriore

Bufferizzato e non: tipicamente decisione a livello locale implementativo. Bloccante: tengo conto

della memoria dedicata alla risposta e aspetto finché non ci sarà la risposta vera e propria. Non

bloccante: non c'è memoria e il messaggio viene buttato via, quindi senza qualità. Il primo ha un

costo superiore al secondo in quanto occupo una certa memoria per la coda di risposte.

Affidabile e non: abbiamo e non abbiamo una certa garanzia che il messaggio non si perda.

44

Parlando di cliente-servitore ciò che deve sempre succedere è che entrambi siano COMPRESENTI.

Mentre, invece normalmente, nello scambio di messaggi non vi è compresenza e il messaggio viene

mantenuto finché non lo si va a leggere.

Per ciò, il modello c/s è considerato di più alto livello, è più facile da usare. Mentre nel modello a

scambio di messaggi è d'obbligo avere una certa conoscenza di opzioni ascritte alla flessibilità di

tale modello ma che rendono più difficile il suo utilizzo: quindi richiede più conoscenza e

competenza lavorare a scambio di messaggi che non in c/s.

In generale, il modello c/s è più trasparente mentre il meccanismo a scambio di messaggi è più

flessibile e quindi potrebbe avere delle implementazioni più primitive ed efficienti.

Per quanto riguarda i middleware ciò che viene proposto all'utilizzatore è un modello c/s ma ai

livelli più bassi tratteremo di scambio di messaggi, stessa cosa anche quando ad esempio vogliamo

ottenere un interazione con un mittente e molti destinatari.

45

Nel distribuito, quando vogliamo comunicare facciamo transitare del contenuto da qualcuno a

qualcun' altro, con tali entità separate e distinte l'una dall'altro. La comunicazione quindi, impone un

accoppiamento tra le parti, introduce cioè dei vincoli di vario genere sulle entità che interagiscono:

accoppiamento di spazio: le due entità devono comunicare conoscendosi reciprocamente ed essere

co-locate, ossia vicine (nella stessa località geografica).

accoppiamento in tempo: deve esserci un intervallo in cui devono esistere le due entità interagenti.

accoppiamento in sincronizzazione: le due entità citate devono avere delle forme di attesa reciproca

(ma non è detto che ciò sia necessario)

Maggiore è l'accoppiamento maggiore è anche l'insieme di vincoli introdotti dalla comunicazione.

Devo pertanto avere dei casi in cui i sistemi di comunicazione siano caratterizzati da un forte

disaccoppiamento che aumenta l'efficienza del sistema.

D'altra parte però, l'accoppiamento di se per sé, ci fornisce le informazioni necessarie sulla

comunicazione, ci dice in sostanza se la comunicazione sta andando bene o meno, cosa che non

avviene con il disaccoppiamento che non ci permette di conoscere cosa succede dall'altra parte.

46--->48

IL modello ad eventi è considerato molto di alto livello.

É un interazione a 3 entità:

(molteplicità di) produttori: generano gli eventi (publish: pubblicazione dell'evento che fluisce da

Reti di Calcolatori LM 11

Page 12: Modelli Ci ò - unibo.itL'ereditariet àè un ottima scelta dal punto di vista della modellazione. Se volessimo, ad esempio, data una classe ed una sua super classe, avere un ulteriore

questi al supporto).

(molteplicità di) consumatori: interessati agli eventi prodotti (subscribe: manifestazione di interesse

al prodotto di uno o più produttori).

sistema di gestione degli eventi;

E' un modello molti-a-molti, molto flessibile (per questo poco usato perché presenta una semantica

difficile ed una difficile gestione) ed è ottimo, e pensato esclusivamente per il distribuito in quanto

presenta un forte disaccoppiamento (non considera le cose a livello locale anche se in microsoft a

basso livello viene utilizzata una specie di modello ad eventi ma non è ASSOLUTAMENTE la

stessa cosa: quest'ultimo è un modello di gestione di messaggi locali!!)

49

In generale, in fase di implementazione, i modelli ad eventi ci introducono un sacco di problemi

perché abbiam a che fare con molte entità. La parte di supporto, fondamentale per la buona

comunicazione tra le singole parti, bisogna che abbia, ove possibile, un costo limitato, ossia che le

performance siano buone e che la scalabilità del sistema sia elevata (aumentando il numero di utenti

iscritti al gestore degli eventi le performance non si abbassino enormemente: i tempi di latenza siano

limitati), che la qualità dello strumento sia buona, che i produttori e i consumatori siano sviluppabili

in modo disaccoppiato e che da dovunque si possa ottenere il servizio.

50

In prima battuta i sistemi ad eventi, vennero pensati per non avere contenuto informativo, in

sostanza gli eventi erano dei segnali inviati dai produttori con l'effetto di smuovere dei consumatori

interessati. Eventi primitivi come gli interrupt di un interfaccia grafica. Erano, in sintesi, segnali

on/off senza contenuto informativo.

In seconda battuta, venne dato del contenuto a tali eventi al fine di avere della comunicazione e

quindi dello scambio di informazioni, ad esempio gli RSS con registrazione su temi specifici o

multicast differenziato su gruppi di destinatari diversi con registrazioni differenziate.

Sempre di più, con l'utilizzo di sistemi applicativi, si è sentito il bisogno di dare della qualità a tali

eventi, ossia di permettere la ricezione di eventi anche quando si è off-line (eventi persistenti) con

un conseguente forte impatto sulla infrastruttura proprio perché, così facendo, c'è bisogno di

memoria necessaria per il mantenimento di tali eventi.

<<per quanto tempo vengono mantenuti?>> Alcuni, quelli con più qualità, per un tempo illimitato,

altri potrebbero essere eliminati dopo un certo periodo. Tutto ciò perché si ha a che fare con

l'allocazione di risorse e ciò comporta un certo costo.

51

Pertanto, un cliente potrebbe essere interessato solo ad un sottoinsieme di tutto quello che produce il

produttore al quale si è sottoiscritto. E' possibile ottenere ciò filtrando i relativi messaggi sulla base:

• di un topic, quindi basandosi su particolari etichette,

• del contenuto stesso degli eventi prodotti.

• del tipo, quindi sul tipo di messaggio.

52-53-54

Sistemi a tuple

12 Stefano Di Monte

Page 13: Modelli Ci ò - unibo.itL'ereditariet àè un ottima scelta dal punto di vista della modellazione. Se volessimo, ad esempio, data una classe ed una sua super classe, avere un ulteriore

Come sappiamo, per la memoria, leggere e scrivere un dato nella stessa area di memoria può

comportare delle interferenze.

Il sistema a tuple nasce con l'idea di impedire l'interferenza fra azioni di lettura e scrittura su certi

contenuti, nel nostro caso (quindi astraendo) molto di alto livello.

Una tupla è di solito costituita da una serie di attributi che caratterizzano la tupla stessa.

Tutte le tuple debbono essere distinte tra di loro e, in generale, se hanno contenuto, questo dev'essere

differente per ogni tupla. Di conseguenza, non posso aggiungere una tupla che abbia lo stesso

contenuto di un'altra tupla già contenuta nello spazio delle tuple.

In uno spazio di tuple le operazioni che si possono eseguire sono IN e OUT.

Lo spazio delle tuple consente di inserire solo tuple in accordo alla specifica della tupla.

Modello dello SPAZIO delle TUPLE

Come già accennato precedentemente, il disaccoppiamento è alla base di un ottimo sistema

distribuito. Il modello dello spazio delle tuple è caratterizzato da una comunicazione fortemente

persistente e disaccoppiata e sincrona, realizzata attraverso un'entità astratta, lo spazio di tuple per

l'appunto, che è una sorta di memoria condivisa nella quale vengono depositate le tuple e le anti-

tuple. Lo spazio delle tuple è quindi un insieme strutturato di relazioni, intese come contenitori di

attributi e valori per PUB-SUB (le varie tuple). Su uno spazio di tuple si possono depositare ed

estrarre informazioni di alto livello senza causare interferenze; inoltre non ci sono limiti alle tuple

che si possono depositare e che possono rimanere nello spazio delle tuple senza limiti di tempo

(quindi i produttori e i consumatori non hanno la necessità di coesistere nello stesso momento

-disaccoppiamento temporale-) e senza limiti di memoria, la cosa importante è che tali tuple

debbono essere uniche tra loro, ossia tutte diverse.

Su uno spazio di tuple è sempre possibile effettuare operazioni di:

• scrittura/inserimento (in)

• lettura/estrazione (out)

• il match avviene in base al pattern (della richiesta) sui valori degli attributi (in caso di match

con più tuple ne viene estratta una sola in maniera non deterministica), di conseguenza lo

spazio delle tuple dovrà memorizzare non solo quest’ ultime ma anche le richieste in attesa,

quelle cioè che non hanno potuto fare matching con alcuna tupla e che sono in attesa

dell’inserimento di una particolare tupla.

Problema di memoria:

Lo spazio delle tuple potrebbe crescere più della effettiva memoria disponibile.

Soluzione: Partizionamento

Le entità, o nodi, sono organizzate in un anello logico, come mostrato in Figura 1, e ciascun nodo è

caratterizzato da un identificatore univoco. I nodi sono ordinati nell'anello in base al loro

identificatore. La comunicazione si basa sullo scambio di messaggi tra nodi adiacenti e segue un

determinato ordine all'interno dell'anello; in caso di comportamento non deterministico, per ogni

operazione di:

• out viene effettuato un giro dell’anello verificando che in tutto l’anello ci sia una tupla che

soddisfi la richiesta, in caso affermativo la tupla viene estratta e letta; se la tupla non è

presente la richiesta viene inserita in una coda.

• in viene effettuato un giro dell’anello verificando che non ci sia già un’altra richiesta per

quella stessa tupla, in caso affermativo la tupla non viene inserita, in caso contrario la tupla

viene inserita.

Reti di Calcolatori LM 13

Page 14: Modelli Ci ò - unibo.itL'ereditariet àè un ottima scelta dal punto di vista della modellazione. Se volessimo, ad esempio, data una classe ed una sua super classe, avere un ulteriore

N.B.

In caso di comportamento deterministico sia per le operazioni di out che di in non viene effettuato

alcun controllo.

Lo spazio di tuple è distribuito tra tutti i nodi e ognuno ha la responsabilità di mantenere un

sottoinsieme delle risorse formato dalle tuple che hanno identificatore compreso tra quello del nodo

(incluso) e quello del suo predecessore (escluso). La struttura del singolo nodo è mostrata in Figura

2. Esso è composto da differenti moduli organizzati gerarchicamente su più livelli. Ciascun modulo

utilizza le funzionalità offerte da uno o più moduli per portare a termine le attività di cui è

responsabile. La comunicazione tra i moduli avviene sia tramite invocazione di metodi (frecce

continue) sia tramite eventi (frecce tratteggiate).

<<E se dovesse cadere un nodo dell’anello?>>

Replicazione del nodo!

Figura1: Organizzazione dei nodi Figura2: struttura del singolo nodo

55

Abbiamo visto come, molto spesso, nei sistemi tendiamo a delegare qualcuno per fare attuare un

servizio che ci può interessare, in particolare quando abbiamo a che fare con dei riferimenti remoti,

utilizziamo dei proxy, degli intermediari quindi, che potrebbero servire, ad esempio, per trasformare

dati, per il passaggio garantito di certi parametri in modo automatico … . I proxy possono quindi

diventare dei piccoli componenti che ci servono per automatizzare quelle piccole funzioni che

tipicamente sono sempre le stesse.

Ciò che in realtà succede è che gli utenti si vedono diminuire la quantità di compiti assegnatili per la

gestione della comunicazione, compiti che passerebbero quindi a questi intermediari, come ad

esempio, la gestione dei riferimenti remoti (come trovarli, dove cercarli), attivazione del servitore

14 Stefano Di Monte

Page 15: Modelli Ci ò - unibo.itL'ereditariet àè un ottima scelta dal punto di vista della modellazione. Se volessimo, ad esempio, data una classe ed una sua super classe, avere un ulteriore

nel caso in cui questo non sia attivo al momento della comunicazione. Tutto ciò potrebbe essere

effettuato da un supporto (qualcosa di più di un proxy RMI) che è al di sotto, che potremmo

chiamare “broker” o comunque gestore di tutto ciò che c'è di sotto per garantire un corretto aggancio

dinamico tra le entità.

Tornando ad RMI:

<<ci potrebbe essere condivisione del proxy lato cliente tra due clienti diversi, facendo riferimento

allo stesso servitore?>>

Certo! Ma nella maggior parte dei sistemi ciò non avviene, quello che succede è che ogni cliente ha,

per ogni server, quindi per ogni tipo di richiesta, il proprio stub, così come da parte servitore ogni

server ha il proprio skeleton. Come conseguenza, si potrebbe arrivare ad avere (come successe in

microsoft) una marea di proxy difficilmente gestibili!

56

Quindi all'utente è lasciata solo la responsabilità di scrivere della logica di qualcosa, che è usabile

solamente all'interno di un contenitore.

Un contenitore è in sostanza il broker del lucido precedente capace di fare delle azioni ed anche di

eseguirle su richiesta per compito di qualcun' altro ossia del componente, il quale a sua volta vive

dove vi è il contenitore che gli dà le funzionalità necessarie per poter funzionare: si crea quindi un

forte legame ma al tempo stesso uno svincolarsi dal progetto completo.

Quando parliamo di componente, parliamo quindi in generale di qualche cosa a grana più grossa di

un oggetto, capace di fornire delle operazioni attuate da un ambiente di servizio che prende il nome

di contenitore, engine, middleware che fornisce una serie di funzionalità senza le quali un

componente non potrebbe vivere.

In generale i contenitori forniscono una serie di azioni ripetitive sempre uguali che facilmente

possono essere delegate a qualcun' altro (ad esempio l'attivazione del servitore nel caso in cui questo

non sia attivo al momento della comunicazione, o la sua sospensione nel caso in cui esso non venga

interrogato per un certo periodo limitando quindi l'impegno di risorse).

All'utilizzatore rimane il compito di specificare solo la parte contenuta che tipicamente, è di alto

livello, non ripetitiva e fortemente dipendente dalla logica applicativa; pertanto il middleware dovrà

astrarre, per forza di cose, da quest'ultima.

57

Quindi, in prima battuta, potremmo pensare che un container, un engine sia un qualche cosa che

abbia una serie di servizi esposti a dei clienti che ne fanno richiesta. Così dicendo si potrebbe

pensare che normalmente lavori localmente ed attivi e disattivi su bisogno dei componenti locali (gli

unici che potrebbero vederlo).

In realtà, i componenti non sono presenti tutti sulla stessa macchina ma possiamo spesso avere o più

container che si coordinano o un unico container istanziato su varie macchine.

Di container ne esistono diversi perché diverse sono le esigenze, ma la logica resta la medesima:

Reti di Calcolatori LM 15

Page 16: Modelli Ci ò - unibo.itL'ereditariet àè un ottima scelta dal punto di vista della modellazione. Se volessimo, ad esempio, data una classe ed una sua super classe, avere un ulteriore

supportare qualcun' altro.

58

Un contenitore in generale quindi, fornisce delle funzioni di supporto, quelle che ci interessano

maggiormente sono:

• supportano il ciclo di vita dei componenti (un componente vive per tutta la durata

dell'applicazione o vive in base al bisogno previsto, serve comunque mantenere il suo stato

per tutta la sua durata e/o non; attivazione/passivazione … ),

• se siamo dinamici bisogna che ci sia un buon sistema di nomi (pertanto il registry di RMI è

molto limitato) e si parla infatti di discovery (il che aumenta la mia visibilità -ad esempio al

contenuto di altri contenitori-), federazione con altri container... .

• supporto alla qualità del servizio (QoS): deve esistere un accordo in cui il servizio fornito

debba soddisfare dei parametri negoziati tra utente finale ed erogatore,; la mancanza di QoS

potrebbe presentare, in genere, il non pagamento del cliente.

59

container locali: ci interessano poco

60

Con TRASPARENZA si intende, se ci si trova nel distribuito, “nascondere le cose, i dettagli”, il che

va in contrasto con la visibilità.

Trasparenza di:

• Accesso: accedo ad una risorsa in locale ma non m'accorgo che è locale.

• Allocazione: non mi accorgo che in realtà la risorsa è allocata da un'altra parte, quindi

quando creo assumo che stia creando sullo stesso mio nodo.

• Nome: identifico con nomi tutti uguali le risorse locali e remote.

• Esecuzione: non mi accorgo se la mia esecuzione sta impegnando risorse locali o remote

• Performance: non mi accorgo, richiedendo un servizio, che questo sia remoto o locale,

avendo quindi una differenza di performance: per tale motivo tale tipo di trasparenza è

difficile da avere perché è difficile non accorgersi del differente livello di performance.

• Fault: capacità di fornire il servizio anche in caso di guasti ---> replicazione.

Più un sistema è grande e meno c'è trasparenza!

Nonostante ciò, la trasparenza è un requisito sempre da cercare!

Fatto sta comunque che sistemi totalmente trasparenti non si sono mai realizzati perché non

avrebbero mai avuto mercato, ciò renderebbe impossibile servizi location-awereness per la

16 Stefano Di Monte

Page 17: Modelli Ci ò - unibo.itL'ereditariet àè un ottima scelta dal punto di vista della modellazione. Se volessimo, ad esempio, data una classe ed una sua super classe, avere un ulteriore

generazioni di servizi dipendenti dalla località di chi richiede il servizio stesso, o anche non

permetterebbe a quei clienti un po' più avanzati di poter vedere dettagli....per tale motivo si parla di

“traslucidità”: chi vuole vedere ciò che c'è sotto lo può fare altrimenti è tutto trasparente.

61 - 62

Negli anni 2000, alcune delle più importanti società americane e internazionali, han deciso che

valeva la pena di cominciare a ridefinire i servizi da fornire agli utenti finali. Lo standard definito è

TINA, nuova architettura di rete per un supporto a uso informatico, descritta da una manualistica

più limitata di quella di OSI ma con delle direzioni molto precise:

innanzitutto non è un modello c/s, è più coordinato, anche se rapporti c/s non mancano, ma questi

non rappresentano l'intera architettura. Ragionando per un cliente solo, l'idea di base è che questi

abbia bisogno di servizi di comunicazione, di fornire quindi banda, ma ciò non basta.

Tale idea viene affiancata da un idea molto precisa di fornire dei servizi su quella banda (provider

di rete (che devono federarsi) + provider di servizi).

In conclusione, tale modello, è molto coordinato, sicuramente con rapporti c/s singoli, ma che è, nel

complesso, più articolato.

Molto importante è l'idea di gestione dei servizi: non si pensa solo a fornire i servizi, ma anche a

controllarli! Da ciò ne deriva una certa qualità del servizio stesso, dipendente da più fattori, quindi

da più provider (di rete e di servizio).

Modelli abbastanza astratti:

63

RAM = Random Access Machine (assomiglia ad una macchina di Von Neuman)

E' una macchina costituita da:

• un programma, quindi una sequenza di istruzioni contenute in determinate locazioni, quindi

di una certa dimensione. Il programma è assolutamente inalterabile.

• una memoria, acceduta in lettura e scrittura dal programma, con celle tutte della stessa

dimensione predeterminate.

• un solo accumulatore, con dimensione anche uguale a quella della memoria

• un nastro di INput

• un nastro di OUTput

Le RAM sono state molto usate per fare della previsione di quello che succede nel distribuito.

Hanno una modalità di indirizzamento vario (indiretto, diretto ecc) ma vi è un vincolo: le istruzioni

Reti di Calcolatori LM 17

Page 18: Modelli Ci ò - unibo.itL'ereditariet àè un ottima scelta dal punto di vista della modellazione. Se volessimo, ad esempio, data una classe ed una sua super classe, avere un ulteriore

eseguono tutte nello stesso tempo (ipotesi molto riduttiva e per ciò, da questo punto di vista, è

quindi una macchina astratta). Tale macchina esegue in sequenza il programma, il quale non ha un

limite di dimensione (cosa astratta) così come per la memoria (entrambe le due cose però son decise

staticamente).

65

PRAM (Parallel RAM)

E' un estensione della RAM.

É composta da:

• una serie di P programmi, tutti diversi tra loro e con estensioni varie con ciascun un

accumulatore differente , ma che condividono tutti la stessa memoria.

• una serie di P accumulatori

• un nastro di input

• un nastro di output

• un unica memoria condivisa (da ciò modello globale) (quindi le locazioni di memoria

possono essere viste da tutti allo stesso modo), che può quindi anche servire per trasferire

informazioni da un programma ad un altro, con possibilità di interferenze.

In una PRAM, i programmi partono dalla prima istruzione, tutti insieme, per poi eseguire tutti

insieme la seconda istruzione, poi la terza (sempre tutti insiemi) e così via: la durata dell'istruzione è

la stessa per tutti programmi; ciò non vuol dire che tutti i programmi terminino tutti insieme perché

potrebbero essere (e lo sono) tutti di dimensioni differenti.

É un modello MIMD fortemente sincrono, in un cui le istruzioni sono eseguite da tutti, una alla

volta, in modo sincrono (e ciò non è una cosa banale!).

66

Per definire meglio la semantica di lavoro bisogna che capiamo cosa succede quando più programmi

18 Stefano Di Monte

Page 19: Modelli Ci ò - unibo.itL'ereditariet àè un ottima scelta dal punto di vista della modellazione. Se volessimo, ad esempio, data una classe ed una sua super classe, avere un ulteriore

accedano alla stessa locazione di memoria, per ciò le operazioni di lettura e scrittura sono:

• o sequenzializzate: due programmi accedono alla stessa locazione di memoria, uno aspetta e

l'altro esegue (ad esempio EREW-ERCW), in sostanza in mutua esclusione.

• o concorrenziale/simultaneo: più p RAM della PRAM (anche tutte) accedono in modo

concorrenziale alla memoria (ad esempio CREW-CRCW).

67

Le operazioni concorrenti funzionano sempre bene (vale solo l'ultimo valore scritto che sarà sempre

ben fatto e non spurio) a differenza del sequenziale, non si parla ancora di interferenza, ma ciò che

succede è che ogni programma scrive ad un ciclo di istruzione comportando un certo numero di

ritardi magari eccessivo se P è grande.

Tale modello presenta forti ipotesi riduttive che non si fanno nel modo reale:

1. eseguire delle istruzioni in modo sincrono è abbastanza complicato da realizzare con

l'aumentare di P.

2. all'aumentare di P non è neanche banale avere un accesso condiviso alla memoria (cosa che

si fa solo per un numero di accessi limitato).

Per ciò, tale modello globale è di difficile realizzazione. Da notare inoltre che c'è una fortissima

condivisione dell'input e dell'output, pertanto il tempo delle operazioni di input/output è trascurato e

l'accesso ad un unico nastro non cresce con l'aumentare di P.

68

MPRAM (MessagePassing RAM)

E' un modello un po' meno condiviso rispetto a PRAM.

É composto da:

• Elimina la memoria globale, ogni programma ha una sua memoria privata (quindi se P

programmi allora P memorie)

• ha P accumulatori,

• un nastro di IN

• ed un nastro di OUT.

Ogni programma potrebbe essere messo in comunicazione con l'altro attraverso dei canali di

comunicazione, i quali dicono che ogni nodo ha un certo numero di vicini: se fossero tre

programmi, allora sarebbero tutti collegati tra di loro ed in quest'ultimo caso, quello che si creerebbe

è un anello.

Reti di Calcolatori LM 19

Page 20: Modelli Ci ò - unibo.itL'ereditariet àè un ottima scelta dal punto di vista della modellazione. Se volessimo, ad esempio, data una classe ed una sua super classe, avere un ulteriore

Con P programmi abbiamo un numero di canali pari a: num_canali = (P*(P-1))/2.

NOTA: i canali sono bidirezionali.

69

Le operazioni nei canali sono delle receive e delle send.

Nelle MPRAM, la semantica è una semantica rendez-vous esteso, ossia chi prima arriva aspetta

l'altro: se faccio una send aspetto che dall'altra parte l'altro faccia match, quindi se devo inviare a

molti: invio al primo ed aspetto che faccia match, poi posso inviare al secondo e aspetto che anche

questo faccia match e così via.

Questo è un modello locale perché non vi è niente di condiviso, non vi è niente di globalmente

accessibile a tutti.

70

L'espressività di un modello globale è molto più larga che quella di un modello locale: le azioni

facili da fare in un modello globale sono più difficili da realizzare in un modello locale.

Difatti in un modello globale è molto più semplice fare del broadcast. In particolare per una

istruzione di broadcast, in un modello MPRAM ogni nodo intermediario deve fare una send ed una

receive, deve fare quindi del routing con le istruzioni che si diffondono punto-punto (realizzando in

tal modo una specie di memoria comune); in un modello PRAM tutto si risolve con un'istruzione (la

capacità espressiva è la stessa ma, ovviamente, l'espressività dei modelli è completamente differente

in termini di quantità).

D'altro canto, in un modello PRAM non possiamo aumentare la memoria condivisa, mentre invece

in un modello MPRAM è possibile aumentare il numero di programmi e quindi di canali.

71

Con le PRAM e le MPRAM si sono realizzato molti teoremi.

In particolare, con le PRAM sono stati creati molti teoremi a memoria condivisa.

Con le MPRAM, è stato creato il teorema del routing ottimo: per andare da un nodo X ad un nodo

Y, X manda un messaggio in modo random a K che a sua volta manda un messaggio random a Y.

<<Cosa vuol dire fare del routing ottimo?>>

Vuol dire, fare del routing che garantisca al meglio la distribuzione del traffico su tutti i possibili

canali. Di conseguenza lavorare random è la cosa migliore, perché se per caso avessi molto traffico

tra X e Y, tale canale, lavorando normalmente, si ingolferebbe sicuramente.

Così facendo sollecitiamo tutti i canali in modo abbastanza bilanciato, cosa che, però, non è mai

20 Stefano Di Monte

Page 21: Modelli Ci ò - unibo.itL'ereditariet àè un ottima scelta dal punto di vista della modellazione. Se volessimo, ad esempio, data una classe ed una sua super classe, avere un ulteriore

vera nella realtà.

72

Quello che si fa quindi, è non innamorarsi troppo del modello, perché poi potrebbe essere difficile

rappresentarlo fedelmente nella realtà!

Utilizziamo quindi degli indicatori che ci forniscano delle stime di cosa ci può dare quel

determinato modello.

Indicatori che saranno richiamati da degli algoritmi dipendenti dalla dimensione N del problema,

con N che può crescere infinitamente.

Tipicamente quando abbiamo un problema, ragionando in base agli indicatori, avremo poi una

soluzione in termini di complessità di tempo T(N), ossia quanto tempo ci mette quell' algoritmo a

risolvere quel problema; altro indicatore utile è la complessità in termini di spazio CS(n) (che noi

utilizzeremo meno).

Parlando sempre di distribuito, potremmo lavorare con un unico processore, soluzione sequenziale

con T1(N), oppure con P processori, soluzione parallela con Tp(N); in generale quindi, l'aggiunta di

processori tende a far diminuire il tempo di esecuzione, quindi Tp(N) < T1(N).

73

Ci sono due indicatori fondamentali che sono la base di tutta la teoria dell'ottimizzazione e sono:

• Lo speed-up è la misura di quanto miglioriamo con l'introduzione di risorse, in particolare si

indica con S(P, N) ed è uguale a

S(P,N)= T(1,N) / T(P,N)

quello che ci aspettiamo è che tale valore sia >1, se così non fosse opteremo quindi per la soluzione

sequenziale.

Nota:

lo speed-up “possibile” è: 0<=speed-up<=P.

lo speed-up “ragionevole”: 1<=speed-up<=P. (Se =1 non abbiamo nessuna miglioria!)

Pertanto, se riuscissimo a spezzare il programma in sottoparti indipendenti tra loro, lo speed-up

totale sarà quindi sicuramente P in quanto, ad esempio, se avessimo 4 processori che si dividono un

quarto dell'applicazione,ognuno ci metterà quindi ¼ del tempo ed ovviamente lo speed-up sarà 4!

• L'efficienza è lo “speed-up” diviso “il numero di processori utilizzati”, ci dice quindi quanto

bene lavorano i processori (se abbiam che tutti i processori lavorano contemporaneamente

allora l'efficienza sarà massima, altrimenti man mano che abbiamo processori idle,

l'efficienza calerà in base a quanti saranno quest'ultimi: se non lavora nessuno l'efficienza è

minima, quindi uguale a 0).

Nota:

l'efficienza è un valore normalizzato: 0<=eff<=1. (quindi se è 0, siam messi male perché abbiamo

introdotto dei processori ma guadagniamo molto poco), rappresenta lo speed-up normalizzato.

74

Tipicamente se lavoriamo in un sistema ideale, lo speed-up è I.

Nota: guardando il grafico, la parte sopra la retta non è possibile.

Reti di Calcolatori LM 21

Page 22: Modelli Ci ò - unibo.itL'ereditariet àè un ottima scelta dal punto di vista della modellazione. Se volessimo, ad esempio, data una classe ed una sua super classe, avere un ulteriore

Cercando una soluzione in un albero quello che si fa tipicamente in sequenziale è cercarla in

profondità esplorando magari tutti i sotto alberi e se la soluzione fosse alla fine dell'ultimo

sottoalbero con costo di esecuzione relativamente alto. Lavorando in parallelo il costo della stessa

esecuzione sarà molto molto minore di quella in sequenziale.

75

legge di grosh:

“Il migliore sviluppo (deployment) per un programma è una esecuzione sequenzializzata su un

unico processore”

Ciò si scontra con il distribuito, dove abbiamo molte risorse e molti vincoli che non permettono di

stare su un unico processore (quest'ultima è soluzione sempre adottata se fattibile).

I due indicatori precedentemente enunciati, speed-up ed efficienza, dipendono da N (dimensione del

problema) e P (numero di processori, grado di parallelizzazione), fattori che possono essere

esaminati in modi separati, indipendenti o dipendenti tra loro:

• Fattore di carico (loading factor): L= N/P

Distribuendo la complessità del problema in modo omogeneo fra i diversi processori otteniamo

l'idea del carico del singolo processore.

I valori N e P sono stati studiati dai sistemi lavorando in:

identity size: N e P hanno lo stesso valore (N == P)

independent size: si stabilisce che N è indipendente da P

dependent size: molto spesso si lavora con N dipendente da P

Ciò che vogliamo quindi è capire qual'è il miglior modo di lavorare!

76-77

legge di Amdhal:

“Se abbiamo un programma lo speed-up che si può ottenere è limitato dalla parte sequenziale”

Nella realtà, il problema dà dei vincoli allo speed-up, vincoli dati dalla parte sequenziale del

problema stesso e non da quella parallela, che limitano lo speed-up.

Anche se il numero dei processori in parallelo fosse infinito, al di sopra di una certa soglia (vedi

slide 77) lo speed-up non può andare: limite imposto dalla parte sequenziale!

22 Stefano Di Monte

Page 23: Modelli Ci ò - unibo.itL'ereditariet àè un ottima scelta dal punto di vista della modellazione. Se volessimo, ad esempio, data una classe ed una sua super classe, avere un ulteriore

NOTA; tutto si svolge al di sotto della linea nera e di quella rossa.

Quindi non vale la pena introdurre un numero alto di processori bensì vale la pena introdurre un

numero di processori pari a quello che realmente serve, perché aumentando il numero di processori

diminuisce l'efficienza (i processori in eccesso lavorano meno e non al pieno delle loro possibilità).

78

Heavily Loaded Limit: THL(N) = infP TP(N)

ciò vuol dire che la complessità in N viene stabilita studiando il sistema al variare di P (in maniera

crescente) al fine di trovare quel valore di P che ci dà complessità minima.

Come suggerisce il nome stesso Heavily Loaded (molto caricato), il valore massimo dello speed-up,

minimo quindi della complessità in tempo parallela, si ottiene caricando molto il processore!

Quindi si lavora bene se il fattore di carico è elevato.

Nota:

La complessità in tempo potrebbe essere divisa in due parti: la parte di computazione TCompP e quella

di comunicazione TCommP (ad esempio tra due canali), pertanto:

TP(N) = TcompP + TCommP

A sua volta la parte di computazione si divide in una parte parallelizzabile TCompPar ed una parte

limitante sequenziale TcompSeq, ossia:

TCompP = TCompPar + TcompSeq

79

esempio: somma di N numeri.

• complessità nel sequenziale: O(N).

• complessità nel parallelo:

immaginiamo un albero binario completo:

Reti di Calcolatori LM 23

Page 24: Modelli Ci ò - unibo.itL'ereditariet àè un ottima scelta dal punto di vista della modellazione. Se volessimo, ad esempio, data una classe ed una sua super classe, avere un ulteriore

ogni nodo è un processore, quindi il numero totale di valori e all'incirca uguale a quello dei

processori P , ed è: P= 2H+1 – 1 con H=numero di livelli.

Lavoriamo in identity size (N == P): il numero dei nodi è uguale al numero dei valori da sommare

(in realtà quasi uguali -per il meno1 della formula per il totale dei cpu-).

I valori da sommare vengono passati dalle foglie, sommati e passati da processore in processore

(che a loro volta sommano i dati) attraverso i rami dell'albero fino alla radice che restituirà il

risultato.

Complessità dell'algoritmo:

TP(N) = O(H) = O(log2(N)) = ~ 2 * log2(N)

(il 2 davanti sta perché due sono le comunicazione verso il nodo padre).

Si noti che la complessità dell'algoritmo è misurata dipendentemente dal numero dei livelli:

H = O(log2(P)) = O(log2(N))

che se elevato ha effetti negativi sulla complessità.

80

Al crescere di N lo speed-up va su, non arriva a P ma comunque si alza.

Per quanto riguarda l'efficienza questa sarà uguale a 1/log2(N), che al crescere di N fa diminuire

l'efficienza che tende a zero. Difatti partendo dalle foglie alla radice, i processori idle aumentano

costantemente, quando siamo alla radice l'efficienza sarà quindi minima perché quasi la totalità dei

processori sarà idle, sarà con le mani in mano perché ha già eseguito il suo compito.

24 Stefano Di Monte

Page 25: Modelli Ci ò - unibo.itL'ereditariet àè un ottima scelta dal punto di vista della modellazione. Se volessimo, ad esempio, data una classe ed una sua super classe, avere un ulteriore

Perciò nel mondo ideale questa è una soluzione che non si adotta mai, anche perché con un numero

di valori elevato sarebbe necessario un numero altrettanto elevato di processori: soluzione

IANACCETTABILE!.

81 – 82 -83

Con N molto maggiore di P stiamo quindi partizionando i nodi, i processori, i quali hanno un lavoro

locale da fare ed una fase di scambio di comunicazione per combinare i dati.

La complessità in tempo, di tale soluzione, è la somma di questi due fattori (lavoro locale + fase di

comunicazione).

Lo speed-up e l'efficienza dipenderanno conseguenzialmente,anche loro, da questi due fattori.

Di conseguenza, lo speed-up tende a P mentre l'efficienza tende a 1.

Concludendo, tanto più i nodi sono caricati tanto meglio lavoriamo in termini di speed-up ed

efficienza.

84 - 85

Quest'ultimo modello è assolutamente un modello ideale, ciò che avviene nel mondo reale non è

questo.

Ad esempio, ciò che abbiamo supposto è che i dati salgono su verso la radice in modo veloce senza

alcun disturbo, supponendo che invece per una qualche ragione un nodo abbia altre cose da fare e la

comunicazione venga ritardata, ciò comporterebbe un effetto di svantaggio generale sul tempo totale

di esecuzione.

Ci sono un sacco di possibilità di peggiorare la situazione “ideale”.

Maggiore è il numero di nodi maggiore è il rischio di avere colli di bottiglia molto pronunciati a

differenza, ad esempio, di un sistema con 10 nodi.

In generale, quando si parla di speed-up ed efficienza si fa sempre riferimento alla media, ma in

alcuni casi ci potrebbe interessare il caso peggiore.

La realtà è completamente diversa dal caso ideale, e ciò è dovuto alla presenza di molti fattori; per

tale motivo gli indicatori di speed-up ed efficienza sono solo il primo passo.

Di sicuro comunque, ragioniamo sempre sul sistema quando questo è a regime, quando cioè è tutto

pronto, tenendo in considerazione che la comunicazione comporta sincronizzazione e quindi ritardo,

peggiorando inevitabilmente le performance.

86

Overhead

Come dev'essere fatto il mappaggio non è tenuto in conto in nessun modo da speed-up ed efficienza,

questi ci danno delle indicazioni di massima, ideali.

Pertanto, un indicatore molto importante è l'indicatore di overhead che tiene in conto le risorse e il

Reti di Calcolatori LM 25

Page 26: Modelli Ci ò - unibo.itL'ereditariet àè un ottima scelta dal punto di vista della modellazione. Se volessimo, ad esempio, data una classe ed una sua super classe, avere un ulteriore

tempo speso in comunicazione.

Supponendo di essere in un'applicazione ideale, ossia in una applicazione infinitamente

parallelizzabile (ovvero che può essere infinitamente divisa in più parti ed ogni parte associata ad

un processore che lavora solo su questa parte ed è indipendente da tutti gli altri processori quindi

senza overhead -in realtà questi hanno bisogno di coordinarsi...-), introdurre un certo numero di

processori ci porta ad introdurre un risparmio che è pari al numero di processori utilizzati; nella

realtà invece non è così: ciò che risulta è che

P*TP(N) > T1(N)

e tanto è maggiore tale prodotto, tanto è maggiore quello che perdiamo rispetto al caso ideale.

In altre parole, se idealmente l' overhead è nullo, quindi T0 = 0 perché P*TP(N) = T1(N), realmente

invece succede che T0 > 0 perché P*TP(N) > T1(N).

Ovviamente non si possono misurare tutte le fonti di overhead (comunicazione, trasferimento

iniziale dei dati, stampa e gestione valori intermedi, raccolta dei risultati...), così ciò che viene in

mente è misurare l' overhead vedendo quanto è maggiore la differenza della complessità in termini

di tempo tra il sequenziale e il parallelo, pertanto il tempo di overhead T0 si misura come:

T0 = |P*TP(N) - 1*T1(N)|

In sostanza diciamo che, se in un caso ideale dovremmo avere una situazione in cui, introducendo

un processore otteniamo un miglioramento sullo speed-up ideale di P, siccome lavoriamo in un

modo reale, questi due fattori non sono uguali e quindi andiamo a considerarne la differenza ossia il

tempo di overhead.

Il tempo di overhead stima tutti i fattori non ideali (comunicazione, tempo di configurazione, tempo

di I/O …) ed è dipendente da P, ossia dal numero di processori. Difatti maggiore è P, maggiore

conseguentemente è anche l' overhead.

87

Nei sistemi veri T0 è difficilmente =0. Giacché non è uguale a 0, possiamo usarlo per esprimere TP

in termini di tempo sequenziale e tempo di overhead.

Quindi a questo punto possiamo calcolare speed-up ed efficienza, le quali dipenderanno

sicuramente da T0.

26 Stefano Di Monte

Page 27: Modelli Ci ò - unibo.itL'ereditariet àè un ottima scelta dal punto di vista della modellazione. Se volessimo, ad esempio, data una classe ed una sua super classe, avere un ulteriore

Come si nota per quanto riguarda l'efficienza: minore è l' overhead più vicino a 1 è l'efficienza

stessa.

88 - 89

caso di studio precedente con P<<N ed in cui si nota che T0 è fortemente dipendente da P: maggiore

è il numero di processori maggiore è l' overhead.

90

Isoeffcienza

L'obiettivo è mantenere costante l'efficienza, Pertanto partendo dalla formula dell'efficienza in

termini di overhead, si ottiene:

Definendo K=((1-E/E)), si vede chiaramente che il tempo di overhead dipende da un fattore K che

consente di ricavarlo a partire da T1.

Tutto questo ragionamento va sotto il nome di sistema isoefficiente: ossia che hanno una efficienza

costante, quindi dei sistemi scalabili (dei sistemi praticamente ideali), sempre efficienti al variare

del numero dei processori.

K è una costante, costante di efficienza, nei sistemi che s'avvicinano molto al caso ideale.

91

Quello che noi vorremmo è quindi che un sistema sia ad efficienza costante e quindi, caratterizzato

da questa costante K.

Se K è piccola, le cose vanno bene perché introduciamo pochi effetti di perdita di risorse

utili (alta scalabilità).

Se K è elevata, il sistema è meno scalabile.

In realtà, nei sistemi reali, K non è mai una costante ed è sempre in funzione del numero dei

processori. Perciò nella realtà, un sistema reale è sempre scarsamente scalabile!

92

<<Data un applicazione con Q processi (Q inteso >>), ed infiniti processori a disposizione, come

gestire l'allocazione dei processori?>>

Sicuramente un numero pari a 1<<P<<Q/2, in quanto è sempre meglio mantenere carichi i

processori: così facendo il mio sistema avrebbe un alta efficienza.

Reti di Calcolatori LM 27

Page 28: Modelli Ci ò - unibo.itL'ereditariet àè un ottima scelta dal punto di vista della modellazione. Se volessimo, ad esempio, data una classe ed una sua super classe, avere un ulteriore

28 Stefano Di Monte