Studio e Realizzazione di Architetture Concorrenti per Sistemi ad ...
Studio e Realizzazione di Architetture Concorrenti per Sistemi ad ...
Studio e Realizzazione di Architetture Concorrenti per Sistemi ad ...
Create successful ePaper yourself
Turn your PDF publications into a flip-book with our unique Google optimized e-Paper software.
UNIVERSITÀ DEGLI STUDI DI PARMA<br />
FACOLTÀ DI INGEGNERIA<br />
CORSO DI LAUREA IN INGEGNERIA INFORMATICA<br />
STUDIO E REALIZZAZIONE<br />
DI<br />
ARCHITETTURE CONCORRENTI<br />
Relatore:<br />
Chiar.mo Prof. AGOSTINO POGGI<br />
Correlatore:<br />
Dott. Ing. GIOVANNI RIMASSA<br />
PER<br />
SISTEMI AD AGENTI<br />
ANNO ACCADEMICO 2000/2001<br />
Tesi <strong>di</strong> Laurea <strong>di</strong>:<br />
FABIO G. ONERI
Alle <strong>per</strong>sone che mi sono care<br />
e che sono state felici<br />
<strong>di</strong> vedere questo giorno<br />
ed a quelle<br />
che certamente lo sarebbero state:<br />
Adriano, Giacomo<br />
e Roberto.
SOMMARIO<br />
1 INTRODUZIONE .................................................................................................. 1<br />
1.1 SISTEMI AD AGENTI ...................................................................................... 1<br />
1.1.1 Gli Agenti e le <strong>di</strong>scipline informatiche............................................ 1<br />
1.1.2 Standard <strong>per</strong> gli ISA ...................................................................... 3<br />
1.1.3 JADE............................................................................................. 4<br />
1.2 ARCHITETTURE CONCORRENTI...................................................................... 4<br />
1.2.1 Cosa si intende <strong>per</strong> concorrenza................................................... 4<br />
1.2.2 Problemi insiti nella concorrenza................................................... 5<br />
2 ARCHITETTURE CONCORRENTI IN PRODOTTI A LARGA DIFFUSIONE ...... 8<br />
2.1 IL MIDDLEWARE ........................................................................................... 8<br />
2.1.1 Cos'è un Middleware..................................................................... 9<br />
2.1.2 Il Middleware come prodotto informatico..................................... 10<br />
2.1.3 Problematiche connesse alla concorrenza.................................. 10<br />
2.2 DCOM...................................................................................................... 11<br />
2.2.1 Introduzione ................................................................................ 11<br />
2.2.2 Panoramica sull'architettura........................................................ 16<br />
2.2.3 Modelli <strong>di</strong> concorrenza ................................................................ 19<br />
2.3 CORBA.................................................................................................... 22<br />
2.3.1 Introduzione ................................................................................ 22<br />
2.3.2 Panoramica sull'architettura........................................................ 23<br />
2.3.3 Specifiche <strong>per</strong> la concorrenza..................................................... 24<br />
2.3.4 Un esempio d'implementazione: ORBacus ................................. 27<br />
2.4 EJB.......................................................................................................... 31<br />
2.4.1 Introduzione ................................................................................ 32<br />
2.4.2 Panoramica sull'architettura........................................................ 33<br />
2.4.3 Specifiche <strong>per</strong> la concorrenza..................................................... 35<br />
2.5 CONFRONTI ............................................................................................... 35<br />
2.5.1 DCOM e CORBA: creare l'infrastruttura <strong>per</strong> la comunicazione ... 36<br />
2.5.2 Modelli <strong>di</strong> concorrenza: flessibilità, semplicità e sicurezza.......... 37
3 JADE.................................................................................................................. 40<br />
3.1 ARCHITETTURA.......................................................................................... 40<br />
3.1.1 La piattaforma FIPA-compliant.................................................... 41<br />
3.1.2 L'amministrazione degli Agenti.................................................... 43<br />
3.1.3 Modello comportamentale........................................................... 44<br />
3.2 CONCORRENZA: MODELLO THREAD-PER-AGENT.......................................... 46<br />
4 MODELLO DI CONCORRENZA PROPOSTO .................................................. 48<br />
4.1 GRANULOSITÀ DELLA CONCORRENZA .......................................................... 48<br />
4.2 STRUMENTI ADOTTATI................................................................................. 49<br />
4.2.1 Il <strong>di</strong>spatcher................................................................................. 49<br />
4.2.2 Lo scheduling ricorsivo................................................................ 50<br />
4.3 MAPPING DI MODELLI CON GLI STRUMENTI SCELTI ......................................... 51<br />
5 IMPLEMENTAZIONE DI RIFERIMENTO........................................................... 52<br />
5.1 EMBEDDEDTHREAD.................................................................................... 52<br />
5.1.1 Scopi e funzionalità ..................................................................... 53<br />
5.1.2 Meccanismi <strong>di</strong> sincronizzazione degli accessi: lock .................... 54<br />
5.1.3 Ispezione e mo<strong>di</strong>fica dello stato .................................................. 55<br />
5.1.4 Transizioni temporizzate ............................................................. 57<br />
5.1.5 Ispezione ed assegnazione dell'attività ....................................... 63<br />
5.1.6 Mutex .......................................................................................... 65<br />
5.2 THREADDISPATCHER.................................................................................. 68<br />
5.2.1 Scopi e funzionalità ..................................................................... 69<br />
5.2.2 Ispezione e mo<strong>di</strong>fica dei parametri del Thre<strong>ad</strong>-Pool ................... 70<br />
5.2.3 Dispatching e gestione dei thre<strong>ad</strong>............................................... 70<br />
5.2.4 Amministrazione delle richieste degli scheduler e dei thre<strong>ad</strong>...... 73<br />
5.2.5 Re<strong>ad</strong>yThre<strong>ad</strong>sManager............................................................... 76<br />
5.2.6 RequestsManager....................................................................... 79<br />
5.3 SCHEDULER............................................................................................... 83<br />
5.3.1 Scopi e funzionalità ..................................................................... 83<br />
5.3.2 La classe astratta........................................................................ 84<br />
5.3.3 Affidamento e rimozione <strong>di</strong> Behaviour......................................... 84
5.3.4 Blocco e risveglio <strong>di</strong> Behaviour ................................................... 87<br />
5.3.5 Scheduling: notifiche, ispezioni, richieste e terminazione ........... 88<br />
5.3.6 Amministrazione dei cambiamenti <strong>di</strong> stato dei Behaviour ........... 92<br />
5.3.7 Cambiamenti <strong>di</strong> stato imposti ai Behaviour dall'esterno .............. 96<br />
5.3.8 Sottoclassi Concrete ................................................................... 99<br />
5.4 GESTIONE DELLE INTERRUZIONI................................................................. 100<br />
5.4.1 Catena ascendente................................................................... 100<br />
5.4.2 Catena <strong>di</strong>scendente .................................................................. 102<br />
5.4.3 Meto<strong>di</strong> non interrompibili ........................................................... 105<br />
5.4.4 Problemi con InterruptedException ........................................... 107<br />
5.5 CLASSI DI UTILITÀ..................................................................................... 112<br />
5.5.1 AssociationTimerObject ............................................................ 113<br />
5.5.2 De<strong>ad</strong>line.................................................................................... 116<br />
5.6 MODIFICHE ALL'AMBIENTE ........................................................................ 117<br />
5.6.1 Behaviour.................................................................................. 117<br />
5.6.2 CompositeBehaviour................................................................. 118<br />
5.6.3 Agent......................................................................................... 120<br />
5.7 TESTING DELL'IMPLEMENTAZIONE.............................................................. 121<br />
6 CONCLUSIONI ................................................................................................ 123<br />
6.1 ULTERIORI SVILUPPI................................................................................. 124<br />
6.2 RINGRAZIAMENTI...................................................................................... 124<br />
7 BIBLIOGRAFIA ............................................................................................... 126
Introduzione - <strong>Sistemi</strong> <strong>ad</strong> Agenti 1<br />
1 Introduzione<br />
Scopo del presente lavoro è un'analisi <strong>di</strong> modelli <strong>di</strong> concorrenza <strong>per</strong> il loro utilizzo in<br />
applicazioni basate sugli agenti. Dopo una panoramica su <strong>di</strong>versi para<strong>di</strong>gmi già<br />
esistenti in applicazioni middleware ampiamente <strong>di</strong>ffuse, quali DCOM, CORBA ed<br />
EJB, ed un loro confronto, si stu<strong>di</strong>erà il caso specifico <strong>di</strong> JADE (Framework <strong>per</strong><br />
agenti sviluppato da TILab e l'università <strong>di</strong> Parma) e possibili miglioramenti<br />
apportabili. Si proporrà quin<strong>di</strong> un nuovo modello <strong>di</strong> concorrenza e relativa<br />
implementazione <strong>di</strong> riferimento.<br />
1.1 <strong>Sistemi</strong> <strong>ad</strong> Agenti<br />
Negli ultimi anni, avvenimenti quali la <strong>di</strong>ffusione <strong>di</strong> Internet, la grande <strong>di</strong>sponibilità<br />
d'informazioni <strong>di</strong>stribuite, le <strong>di</strong>ffuse potenze <strong>di</strong> calcolo <strong>di</strong>sponibili anche sul lato degli<br />
utenti ed il crescente livello d'astrazione <strong>di</strong> applicativi, capaci <strong>di</strong> offrire interfacce<br />
uomo-macchina sempre più intuitive ed "intelligenti", hanno portato alla luce un<br />
nuovo para<strong>di</strong>gma <strong>per</strong> lo sviluppo <strong>di</strong> sistemi, basato sulla coo<strong>per</strong>azione <strong>di</strong> entità<br />
autonome intelligenti, capaci <strong>di</strong> muoversi in rete, re<strong>per</strong>ire informazioni, collaborare tra<br />
loro: queste entità sono gli Agenti.<br />
1.1.1 Gli Agenti e le <strong>di</strong>scipline informatiche<br />
Nella <strong>di</strong>sciplina dell'Intelligenza Artificiale, e nelle sue molteplici definizioni, centrale è<br />
il ruolo giocato dagli Agenti. L'Artificial Intelligence (AI) si propone, infatti, come<br />
"quella parte della computer science il cui scopo è costruire Agenti che mostrino<br />
comportamento intelligente".<br />
Questa definizione, che rimane volutamente nel vago <strong>per</strong> consentire una più ampia<br />
generalizzazione, non spiega <strong>per</strong>ò cosa s'intenda <strong>per</strong> Agente. Esistono in realtà varie<br />
definizioni <strong>per</strong> questo concetto, dalle più deboli e generiche come "entità autorizzata<br />
<strong>ad</strong> agire <strong>per</strong> conto d'altre parti” (espressione utilizzabile <strong>ad</strong> esempio anche <strong>per</strong> gli<br />
"agenti finanziari"), fino <strong>ad</strong> arrivare a specializzazioni tecniche <strong>di</strong> abilità e
Introduzione - <strong>Sistemi</strong> <strong>ad</strong> Agenti 2<br />
comportamenti che caratterizzano l'Agente stesso. Nell'ambito del presente lavoro ci<br />
si limiterà a considerare gli Agenti Software Intelligenti, dove con tale espressione si<br />
intendono "agenti artificiali o<strong>per</strong>anti, con tecniche <strong>di</strong> AI, in un ambiente software“.<br />
Questo recente para<strong>di</strong>gma <strong>ad</strong> Agenti è assorto <strong>ad</strong> importanza nella<br />
computer science principalmente <strong>per</strong> alcune peculiarità insite nella definizione quali:<br />
?? Autonomia: un agente ha controllo <strong>di</strong>retto sulle proprie azioni e stato interno, ed è<br />
capace <strong>di</strong> comportamenti reattivi e pro-attivi. Può, cioè, <strong>per</strong>cepire l'ambiente in cui<br />
o<strong>per</strong>a e rispondere ai suoi stimoli, ed è capace <strong>di</strong> mostrare comportamenti<br />
goal-<strong>di</strong>rected prendendo quin<strong>di</strong> iniziative.<br />
?? Delega: agisce, possibilmente con competenza, <strong>per</strong> conto <strong>di</strong> altri.<br />
Oltre alle suddette, altre caratteristiche rendono il modello particolarmente<br />
vantaggioso rispetto a meto<strong>di</strong> classici, come <strong>ad</strong> esempio:<br />
?? Mobilità: la capacità <strong>di</strong> alcuni Agenti <strong>di</strong> muoversi da un ambiente <strong>ad</strong> un altro (Es.<br />
sfruttando Internet) verso dati e risorse, evitando le tra<strong>di</strong>zionali migrazioni dei dati,<br />
ed ottimizzando <strong>di</strong> conseguenza le prestazioni.<br />
?? Socialità: capacità <strong>di</strong> creare istanze multiple che o<strong>per</strong>ino in modo collaborativo<br />
parallelizzando il lavoro.<br />
Queste proprietà rendono gli Agenti particolarmente <strong>ad</strong>atti a scenari in cui siano<br />
preponderanti la mole <strong>di</strong> lavoro e l'esibizione <strong>di</strong> comportamenti intelligenti (in modo<br />
specifico nelle interazioni con gli utenti). Tipici esempi <strong>di</strong> problemi che meglio si<br />
<strong>ad</strong>attano <strong>ad</strong> essere risolti usando gli Agenti Software sono:<br />
?? <strong>Sistemi</strong> computerizzati <strong>per</strong> il controllo del traffico aereo.<br />
?? Personal <strong>di</strong>gital assistant (PDA).<br />
?? Re<strong>per</strong>imento <strong>di</strong> documenti (data filtering).<br />
Gli attributi che si richiedono <strong>ad</strong> un Intelligent Software Agent (ISA) sono certamente<br />
più consoni alla descrizione che si farebbe <strong>di</strong> un essere umano, che non a quella <strong>di</strong><br />
un prodotto informatico: esigiamo, infatti, qualità quali autonomia, capacità <strong>di</strong><br />
prendere iniziative, mobilità, abilità sociali come comunicazione e contrattazione,<br />
ecc. Ci si riferisce quin<strong>di</strong> <strong>ad</strong> un ISA come <strong>ad</strong> un Sistema Intenzionale, un sistema<br />
cioè il cui comportamento "può essere predetto e spiegato attraverso l’attribuzione <strong>di</strong><br />
atteggiamenti quali convinzioni, desideri, s<strong>per</strong>anze, paure, ecc.”. Bisogna ricordare
Introduzione - <strong>Sistemi</strong> <strong>ad</strong> Agenti 3<br />
<strong>per</strong>ò che non si tratta <strong>di</strong> sterile antropomorfismo, in quanto "attribuire <strong>ad</strong> una<br />
macchina desideri e convinzioni, … è utile quando questo possa aiutarci a<br />
comprenderne la struttura, comportamenti, e possibili sviluppi, … o quando si<br />
applichi <strong>ad</strong> entità la cui struttura sia solo parzialmente nota” [Bibl. 1] [Bibl. 2].<br />
1.1.2 Standard <strong>per</strong> gli ISA<br />
Quanto detto sopra può far intuire quanta complessità si celi <strong>di</strong>etro la realizzazione <strong>di</strong><br />
un'applicazione basata sugli Agenti, e come possa essere complicato creare le<br />
infrastrutture, e le standar<strong>di</strong>zzazioni necessarie <strong>per</strong> l'implementazione delle attitu<strong>di</strong>ni<br />
<strong>di</strong> un ISA. Per comprendere tali problemi bisogna focalizzarsi su due punti<br />
fondamentali:<br />
1. Per implementare la Mobilità, un Agente deve essere capace <strong>di</strong> comunicare, in<br />
maniera in<strong>di</strong>pendente dalla tecnologia, con macchine hardware eterogenee, deve<br />
essere capace <strong>di</strong> serializzarsi (trasformare la propria struttura ed il proprio stato<br />
interno in dati che possano essere trasmessi alla macchina <strong>di</strong> destinazione, ed<br />
usati in seguito <strong>per</strong> ricreare l'Agente), deve poter invocare azioni su agenti<br />
residenti su altre piattaforme, ecc.<br />
2. Per acquisire Socialità deve poter contare su un linguaggio e protocolli comuni tra<br />
Agenti <strong>di</strong>versi, utilità <strong>per</strong> l'in<strong>di</strong>viduazione degli Agenti presenti su una piattaforma<br />
e dei servizi che possono offrire, ecc.<br />
Dovendo standar<strong>di</strong>zzare queste capacità, <strong>di</strong>versi gruppi <strong>di</strong> ricerca sono stati costituiti<br />
con l'intento <strong>di</strong> esplorare le problematiche insite in tali scenari, e sono infine giunti (i<br />
lavori sono comunque in costante sviluppo) a documenti ormai largamente accettati<br />
come riferimento (anche <strong>per</strong>ché non legati <strong>ad</strong> alcun particolare produttore, ma<br />
"a<strong>per</strong>ti"). Tra le <strong>di</strong>verse standar<strong>di</strong>zzazioni, <strong>di</strong> notevole <strong>di</strong>ffusione sono le specifiche<br />
FIPA <strong>per</strong> l'intero<strong>per</strong>abiltà tra applicazioni basate sugli Agenti, e lo standard CORBA<br />
<strong>per</strong> la creazione delle infrastrutture necessarie agli ambienti basati sugli agenti [Bibl.<br />
3] [Bibl. 12].
Introduzione - <strong>Architetture</strong> <strong>Concorrenti</strong> 4<br />
1.1.3 JADE<br />
JADE (Java Agent DEvelopment Framework) è un'interfaccia <strong>di</strong> sviluppo software<br />
pensata <strong>per</strong> facilitare il compito <strong>di</strong> quei programmatori che intendano sviluppare<br />
applicazioni basate sul modello degli Agenti. Il Framework è conforme agli standard<br />
FIPA, ed è interamente scritto usando Java <strong>per</strong> sfruttarne le funzionalità <strong>di</strong><br />
programmazione object-oriented in ambienti <strong>di</strong>stribuiti eterogenei (serializzazione ed<br />
RMI).<br />
L'utilizzatore del Framework può quin<strong>di</strong> trovare un valido supporto <strong>per</strong> lo sviluppo<br />
dell'applicazione, <strong>di</strong>sinteressandosi degli aspetti <strong>di</strong> basso livello, e concentrando il<br />
lavoro <strong>di</strong>rettamente a sull'organizzazione della comunità d'agenti da lui pensata. Il<br />
prodotto contiene, infatti, una piattaforma FIPA-compliant funzionante, e pacchetti<br />
Java <strong>per</strong> lo sviluppo <strong>di</strong> Agenti: il progettista non dovrà fare altro che usare le classi<br />
fornite (naturalmente sviluppando il tutto <strong>per</strong> mezzo del linguaggio Java) come<br />
classi-base <strong>per</strong> i suoi Agenti, ed usare la piattaforma <strong>per</strong> la messa o<strong>per</strong>ativa del tutto.<br />
La piattaforma è inoltre provvista <strong>di</strong> alcuni utili strumenti (naturalmente Agenti) che<br />
ne facilitano la monitorizzazione <strong>per</strong> le fasi <strong>di</strong> testing e debugging delle applicazioni<br />
[Bibl. 5].<br />
1.2 <strong>Architetture</strong> <strong>Concorrenti</strong><br />
La concorrenza è un meccanismo che consente a più entità <strong>di</strong> svolgere o<strong>per</strong>azioni<br />
<strong>di</strong>fferenti nello stesso tempo. Può portare <strong>ad</strong> un sistema benefici quali collaborazioni<br />
tra entità, miglioramento delle prestazioni ed efficacia nell'utilizzo delle risorse, ma<br />
anche <strong>di</strong>versi problemi se non amministrata correttamente [Bibl. 4].<br />
1.2.1 Cosa si intende <strong>per</strong> concorrenza<br />
In un sistema o<strong>per</strong>ativo, i <strong>di</strong>versi compiti che la macchina può eseguire sono detti<br />
processi. Un processo è essenzialmente un programma eseguito dal calcolatore. Nei<br />
primi sistemi o<strong>per</strong>ativi ogni processo impegnava totalmente la macchina, e doveva<br />
essere eseguito singolarmente su <strong>di</strong> essa, senza che altri compiti potessero essere
Introduzione - <strong>Architetture</strong> <strong>Concorrenti</strong> 5<br />
avviati prima della terminazione <strong>di</strong> quello in corso. Dal momento che un programma<br />
non occupa necessariamente al pieno delle proprie possibilità le risorse <strong>di</strong> un<br />
computer, ma è formato essenzialmente da sezioni <strong>di</strong> calcolo (che impegnano la<br />
CPU) ed input/output (su memorie e <strong>per</strong>iferiche) che possono richiedere attese,<br />
acc<strong>ad</strong>e che, durante l'esecuzione <strong>di</strong> un processo, le risorse <strong>di</strong> una macchina passino<br />
da momenti <strong>di</strong> attività a <strong>per</strong>io<strong>di</strong> d'inattività.<br />
I moderni sistemi, sfruttando tali pause nelle attività <strong>di</strong> CPU e memoria, consentono<br />
<strong>ad</strong> un calcolatore <strong>di</strong> eseguire più processi contemporaneamente, e quin<strong>di</strong> <strong>di</strong><br />
ottimizzare l'uso delle risorse <strong>di</strong> macchina (questo non è <strong>per</strong>ò l'unico strumento<br />
<strong>ad</strong>ottato <strong>per</strong> ottenere il parallelismo)<br />
Due o più processi, <strong>per</strong> i quali l'inizio dell'esecuzione dell'uno avviene prima che altri<br />
abbiano terminato, sono detti concorrenti. I processi possono essere in<strong>di</strong>pendenti od<br />
interagenti. Processi interagenti creano complessivamente un nuovo stato<br />
nell'esecuzione, che <strong>di</strong>fferisce da quello che si avrebbe se fossero eseguiti<br />
sequenzialmente: i processi si con<strong>di</strong>zionano vicendevolmente.<br />
Secondo il tipo <strong>di</strong> interazione che si ha tra processi, questi possono essere:<br />
?? Collaboranti: quando le interazioni sono preve<strong>di</strong>bili e desiderate<br />
?? In competizione: se è presente l'uso <strong>di</strong> risorse comuni, ma non usabili<br />
simultaneamente<br />
?? Interferenti: quando l'interazione non è né prevista, né desiderata, ma dovuta <strong>ad</strong><br />
errori <strong>di</strong> qualche genere.<br />
1.2.2 Problemi insiti nella concorrenza<br />
Quando si ha a che fare con processi, o parti <strong>di</strong> processi, interagenti, si deve<br />
amministrare un sistema il cui comportamento è maggiore della somma dei<br />
comportamenti delle parti. Sezioni <strong>di</strong> programmi che funzionano correttamente<br />
quando eseguite ognuna <strong>per</strong> suo conto, possono risultare interferenti se eseguite<br />
contemporaneamente su thre<strong>ad</strong> <strong>di</strong>versi. Lo stu<strong>di</strong>o <strong>di</strong> tali con<strong>di</strong>zioni non è sempre<br />
agevole, in quanto ogni esecuzione ha una componente aleatoria che la rende
Introduzione - <strong>Architetture</strong> <strong>Concorrenti</strong> 6<br />
<strong>di</strong>fficilmente riproducibile: un problema <strong>di</strong> interferenza potrebbe non presentarsi <strong>per</strong><br />
<strong>di</strong>verse esecuzioni e manifestarsi quando meno lo si aspetta.<br />
Bisogna, innanzi tutto, tenere conto <strong>di</strong> quelle risorse e variabili con<strong>di</strong>vise che<br />
possono essere accessibili da più linee d'esecuzione <strong>di</strong>fferenti. Molte risorse, infatti,<br />
<strong>per</strong> essere utilizzabili correttamente, hanno la necessità <strong>di</strong> "servire" un solo client <strong>per</strong><br />
volta, ed eseguire i <strong>di</strong>versi coman<strong>di</strong> che questo invia loro senza che nuovi utenti<br />
possano frapporvene altri. Per il mantenimento della coerenza interna è spesso<br />
necessario che un set <strong>di</strong> più o<strong>per</strong>azioni sia considerato atomicamente: nasce così<br />
l'esigenza <strong>di</strong> ottenere la mutua esclusione su parti <strong>di</strong> co<strong>di</strong>ce che sono chiamate<br />
"sezioni critiche".<br />
L'accesso a questi aggregati <strong>di</strong> o<strong>per</strong>azioni (o sessioni d'utilizzo <strong>di</strong> risorse macchina),<br />
è regolato da costrutti <strong>di</strong> sincronizzazione che ne garantiscono un accesso<br />
sequenzializzato: questi possono o<strong>per</strong>are a <strong>di</strong>versi livelli <strong>di</strong> astrazione e<br />
comprendono algoritmi specifici, semafori, mutex, regioni critiche, monitor, etc.<br />
Prescindendo dallo strumento utilizzato, le o<strong>per</strong>azioni <strong>di</strong> sincronizzazione, che<br />
comportano tutte possibili attese <strong>per</strong> l'accesso alle risorse (visto che queste devono<br />
essere utilizzate sequenzialmente), possono essere fonte <strong>di</strong> svariati problemi che<br />
vanno da uno scorretto utilizzo degli accessi (con conseguente incoerenza <strong>di</strong> dati), a<br />
situazioni <strong>di</strong> stallo <strong>per</strong> tutto un sistema.<br />
Quest'ultimo problema, chiamato "de<strong>ad</strong>lock", o "blocco critico", anche se<br />
ampiamente trattato nella letteratura dei sistemi o<strong>per</strong>ativi, è particolarmente grave e<br />
<strong>di</strong> <strong>di</strong>fficile in<strong>di</strong>viduazione, in quanto ogni situazione è particolare e va analizzata nel<br />
complesso delle proprie interazioni. Un de<strong>ad</strong>lock può avvenire, <strong>ad</strong> esempio, quando,<br />
dati due thre<strong>ad</strong> d'esecuzione (T1 e T2) e due risorse (A e B), con A posseduta da T1<br />
e B posseduta da T2, T1 cerchi <strong>di</strong> ottenere anche B (entrando nella sua coda <strong>di</strong><br />
attesa ed aspettando che B sia liberata da T2) mentre, simultaneamente, T2 richieda<br />
A: i thre<strong>ad</strong> si bloccano entrambi aspettando interminabilmente che l'uno liberi l'altro.<br />
Il controllo e la verifica della presenza <strong>di</strong> situazioni come quella descritta, comporta,<br />
<strong>per</strong> il programmatore, un'approfon<strong>di</strong>ta analisi <strong>di</strong> tutte le possibili interazioni tra<br />
componenti e dei loro casi d'uso, e può risultare molto impegnativa. In fase <strong>di</strong><br />
progetto <strong>di</strong>venta, quin<strong>di</strong>, molto importante dosare le prestazioni ottenibili dalla
Introduzione - <strong>Architetture</strong> <strong>Concorrenti</strong> 7<br />
concorrenza con la semplicità nella manutenzione dello stesso: anche piccole<br />
mo<strong>di</strong>fiche nella sequenza d'esecuzione <strong>di</strong> alcune o<strong>per</strong>azioni possono essere, infatti,<br />
fonte <strong>di</strong> blocchi critici.
<strong>Architetture</strong> <strong>Concorrenti</strong> in prodotti a larga <strong>di</strong>ffusione - Il Middleware 8<br />
2 <strong>Architetture</strong> <strong>Concorrenti</strong> in prodotti a larga<br />
<strong>di</strong>ffusione<br />
Questo capitolo presenta una panoramica su come alcuni tra i prodotti informatici<br />
middleware più <strong>di</strong>ffusi sul mercato affrontino la questione della concorrenza ed i<br />
problemi correlati con tali scelte. Dopo una trattazione generale della questione,<br />
saranno esposte brevemente le caratteristiche architetturali <strong>di</strong> ogni prodotto, ed una<br />
specifica visione degli aspetti <strong>di</strong> gestione dei thre<strong>ad</strong>.<br />
2.1 Il Middleware<br />
L'attuale scenario degli ambienti orientati alla computazione si presenta alquanto<br />
eterogeneo <strong>per</strong> hardware, sistemi o<strong>per</strong>ativi, tecnologie <strong>di</strong> rete e linguaggi <strong>di</strong><br />
programmazione. Le ragioni <strong>di</strong> queste <strong>di</strong>versità sono solitamente storiche: le<br />
innovazioni tecnologiche e le innumerevoli scelte organizzative ed implementative,<br />
unite alla ricerca dell'ottimizzazione <strong>per</strong> ambienti specifici degli strumenti da<br />
utilizzare, hanno portato <strong>ad</strong> uno sviluppo incrementale, livello su livello, fino a<br />
giungere alle attuali <strong>di</strong>versità. Ambienti specifici hanno, <strong>per</strong> tra<strong>di</strong>zione, <strong>ad</strong>ottato, e<br />
continuato <strong>ad</strong> utilizzare e sviluppare, ambienti oramai consolidati, e l'impostazione<br />
generale sarebbe continuata <strong>ad</strong> essere questa, se non fossero emerse, a livello<br />
planetario, esigenze <strong>di</strong> unificazione tra scomparti che erano considerati im<strong>per</strong>meabili<br />
tra loro.<br />
Punto <strong>di</strong> svolta può considerarsi l'enorme <strong>di</strong>ffusione della rete Internet tra gli utenti <strong>di</strong><br />
sistemi informatici, e soprattutto i suoi risvolti commerciali sulla popolazione in<br />
generale. Le varie realtà hanno cominciato a comunicare tra loro, e sono emersi tutti<br />
i problemi correlati a tali <strong>di</strong>versità: applicativi sviluppati <strong>per</strong> un ambiente devono<br />
essere mo<strong>di</strong>ficati tante volte quanti sono gli ambienti <strong>di</strong>versi sui quali devono essere<br />
utilizzati, così i lati client <strong>di</strong> molte applicazioni client-server erano sempre <strong>di</strong>versi e<br />
non sempre il risultato era paragonabile a quello ottenuto su altre piattaforme.
<strong>Architetture</strong> <strong>Concorrenti</strong> in prodotti a larga <strong>di</strong>ffusione - Il Middleware 9<br />
Non potendo costringere realtà <strong>di</strong>verse <strong>ad</strong> uniformarsi <strong>ad</strong> un unico ambiente, il<br />
processo d'integrazione si è orientato allo sviluppo <strong>di</strong> applicativi <strong>di</strong> interfaccia, che<br />
forniscano un livello comune sopra il quale ogni risorsa, anche se <strong>di</strong>versa <strong>per</strong><br />
tecnologia, possa essere vista nello stesso modo: il primo passo è stato il linguaggio<br />
Java (con la sua JVM), ma realtà più complesse hanno necessità <strong>di</strong> strumenti più<br />
potenti [Bibl. 7].<br />
2.1.1 Cos'è un Middleware<br />
Un middleware è un nuovo concetto <strong>di</strong> applicativo che si pone a metà str<strong>ad</strong>a, come il<br />
nome stesso <strong>di</strong>chiara, tra le <strong>di</strong>versità <strong>di</strong> un mondo formato da utenti eterogenei, e le<br />
esigenze d'integrazione degli sviluppatori <strong>di</strong> programmi a larghissima <strong>di</strong>ffusione: è<br />
sostanzialmente un "semilavorato informatico" da utilizzare <strong>per</strong> la creazione <strong>di</strong><br />
prodotti finiti fruibili dagli utenti [Diagramma 1].<br />
Diagramma 1 : Scenario d'uso del middleware<br />
Si può <strong>di</strong>videre il lavoro necessario <strong>per</strong> la creazione <strong>di</strong> un applicativo in due momenti<br />
logici separati: la realizzazione delle funzionalità specifiche che caratterizzano il<br />
programma, e la creazione dell'infrastruttura che ne sorregge l'insieme. In
<strong>Architetture</strong> <strong>Concorrenti</strong> in prodotti a larga <strong>di</strong>ffusione - Il Middleware 10<br />
quest'ultima fase sono comprese le esigenze d'integrazione, le funzioni <strong>di</strong> basso<br />
livello <strong>per</strong> gestire le comunicazioni in rete, lo sviluppo <strong>di</strong> servizi d'utilità ed<br />
amministrazione delle risorse, etc.: tutte cose che vanno al <strong>di</strong> là dello sviluppo delle<br />
funzioni <strong>di</strong> un prodotto software, ma che sono necessarie (e soprattutto potrebbero<br />
essere riutilizzabili).<br />
Un middleware si occupa <strong>di</strong> incapsulare questo livello sollevando il programmatore<br />
da compiti "te<strong>di</strong>osi e <strong>di</strong>fficoltosi", <strong>per</strong>mettendogli, invece, <strong>di</strong> concentrarsi sulle<br />
funzionalità <strong>ad</strong> alto livello.<br />
2.1.2 Il Middleware come prodotto informatico<br />
In realtà non esiste un unico middleware <strong>ad</strong>atto alle esigenze <strong>di</strong> tutti, ma i numerosi<br />
prodotti sul mercato si <strong>di</strong>fferenziano <strong>per</strong> le scelte implementative effettuate, la<br />
flessibilità offerta al programmatore nel regolare i parametri <strong>di</strong> funzionamento,<br />
caratteristiche <strong>di</strong> sicurezza e robustezza, etc. Tutti hanno, <strong>per</strong>ò, in comune l'esigenza<br />
<strong>di</strong> una standar<strong>di</strong>zzazione dell'interfaccia <strong>di</strong> comunicazione, senza la quale verrebbe<br />
meno lo scopo dell'utilizzo <strong>di</strong> tali strumenti: si è quin<strong>di</strong> assistito alla nascita <strong>di</strong> vari<br />
standard <strong>di</strong> fatto o promossi da appositi consorzi.<br />
I middleware che <strong>di</strong>chiarano <strong>di</strong> conformarsi <strong>ad</strong> una data specifica (o<strong>per</strong>ando una<br />
scelta tra le possibili) con<strong>di</strong>vidono la stessa interfaccia, e possono comunicare tra<br />
loro anche se sviluppati da software house <strong>di</strong>fferenti.<br />
Lo sviluppatore che intende usufruire <strong>di</strong> un middleware, si trova in ogni caso <strong>di</strong> fronte<br />
<strong>ad</strong> una scelta, da prendere in base agli standard <strong>ad</strong>ottati ed alle caratteristiche<br />
specifiche dell'implementazione.<br />
2.1.3 Problematiche connesse alla concorrenza<br />
Un middleware si occupa principalmente <strong>di</strong> sollevare il programmatore da aspetti<br />
troppo tecnici <strong>per</strong> gli scopi che si è prefisso. Questo non significa che tali aspetti<br />
siano trascurati, anzi vengono incapsulati nel middleware e, o<strong>per</strong>ando determinate<br />
scelte, presentate in una forma semplificata, che riduca errori <strong>di</strong> programmazione e<br />
<strong>di</strong> scorretto utilizzo. Le opzioni lasciate al programmatore, pensate dallo sviluppatore
<strong>Architetture</strong> <strong>Concorrenti</strong> in prodotti a larga <strong>di</strong>ffusione - DCOM 11<br />
<strong>di</strong> middleware, caratterizzano ulteriormente il "pacchetto d'utilità" e possono orientare<br />
i fruitori a preferire un middleware <strong>ad</strong> un altro. Molta cura è messa nella flessibilità ed<br />
efficienza offerta dal sistema, senza trascurare semplicità d'uso e robustezza agli<br />
errori (caratteristiche che richiedono spesso un tr<strong>ad</strong>e-off).<br />
La concorrenza fa parte <strong>di</strong> quelle caratteristiche che possono incrementare molto<br />
l'efficienza <strong>di</strong> un sistema, ma, nello stesso tempo, può causare seri problemi se<br />
amministrata scorrettamente [par. 1.2.2 Problemi insiti nella concorrenza].<br />
Un middleware può scegliere <strong>di</strong> consentire che l'utente scelga liberamente il gr<strong>ad</strong>o <strong>di</strong><br />
concorrenza desiderato, o negargli ogni accesso a tale aspetto, ma l'importante è<br />
che, qualunque sia la scelta, il sistema sia in gr<strong>ad</strong>o <strong>di</strong> consentire una buona<br />
prevenzione degli errori (così frequenti in interazioni tanto delicate) ed un facile<br />
controllo sui parametri a <strong>di</strong>sposizione.<br />
2.2 DCOM<br />
Distributed COM è un'estensione del Component Object Model (COM) <strong>per</strong><br />
supportare le comunicazioni tra oggetti <strong>di</strong>stribuiti su macchine <strong>di</strong>fferenti, collegate<br />
tramite reti locali (LAN) od anche Internet. Entrambi i prodotti sono stati sviluppati<br />
dalla Microsoft Corporation e forniscono non solo un insieme <strong>di</strong> specifiche ma<br />
anche implementazioni e conseguenti prodotti informatici pronti all'uso. DCOM, come<br />
<strong>di</strong> consueto, si occupa <strong>di</strong> tutti i dettagli <strong>di</strong> basso livello riguardanti le comunicazioni in<br />
rete, la gestione delle risorse, etc., lasciando che il programmatore si concentri sulle<br />
funzionalità aggiuntive.<br />
2.2.1 Introduzione<br />
COM è uno standard mirato all'uso <strong>di</strong> componenti: stabilisce i contratti base <strong>per</strong><br />
garantire una corretta comunicazione tra moduli software e relativi client. Lo scopo <strong>di</strong><br />
tale standar<strong>di</strong>zzazione è quello <strong>di</strong> creare un'interfaccia comune tra moduli, che<br />
possono essere forniti da <strong>di</strong>versi produttori anche in tempi <strong>di</strong>versi, e garantirne riuso,<br />
collaborazione e composizione reciproca secondo le esigenze dei consumatori.
<strong>Architetture</strong> <strong>Concorrenti</strong> in prodotti a larga <strong>di</strong>ffusione - DCOM 12<br />
La standar<strong>di</strong>zzazione avviene a basso livello (si parla <strong>di</strong> compatibilità della struttura<br />
binaria), lasciando totale libertà <strong>di</strong> scelta sul linguaggio <strong>di</strong> sviluppo (C++, Java, Visual<br />
Basic,… ): tale caratteristica è nota come "neutralità rispetto al linguaggio".<br />
Ulteriore flessibilità è data dal supporto al "versioning" che consente un'ampia<br />
scalabilità nell'evenienza <strong>di</strong> "mo<strong>di</strong>fiche migliorative" dei prodotti-componenti. Nel<br />
caso in cui sia necessario apportare mo<strong>di</strong>fiche o nuove funzionalità <strong>ad</strong> un<br />
componente già sviluppato, l'approccio tra<strong>di</strong>zionale consisterebbe in mo<strong>di</strong>fiche sia<br />
sul lato server-componente, sia su quello client (<strong>per</strong> ogni utilizzatore della risorsa)<br />
con molto lavoro da parte degli amministratori <strong>di</strong> sistema. COM consente ai moduli <strong>di</strong><br />
presentarsi con interfacce <strong>di</strong>fferenti a seconda dei <strong>di</strong>versi client: vecchi utenti<br />
possono <strong>ad</strong>o<strong>per</strong>are i soliti meto<strong>di</strong> su nuove implementazioni (sfruttabili pienamente<br />
dopo gli eventuali aggiornamenti dei client), viceversa, nuovi utilizzatori possono<br />
ancora <strong>ad</strong>o<strong>per</strong>are versioni obsolete <strong>di</strong> un servizio [Diagramma 2].<br />
Diagramma 2 : Versioning<br />
Altri importanti supporti alla scalabilità sono forniti dalle caratteristiche <strong>di</strong>:<br />
?? Location Independence: le chiamate <strong>ad</strong> un componente sono fatte, dal client, allo<br />
stesso modo in<strong>di</strong>pendentemente dal fatto che il servizio risieda nello stesso<br />
"processo", o su <strong>di</strong> una macchina accessibile tramite web. Il co<strong>di</strong>ce non ha
<strong>Architetture</strong> <strong>Concorrenti</strong> in prodotti a larga <strong>di</strong>ffusione - DCOM 13<br />
nemmeno bisogno <strong>di</strong> essere ricompilato, ma basta mo<strong>di</strong>ficare alcuni parametri <strong>di</strong><br />
macchina [Diagramma 3].<br />
Diagramma 3 : Location independence
<strong>Architetture</strong> <strong>Concorrenti</strong> in prodotti a larga <strong>di</strong>ffusione - DCOM 14<br />
?? Flexible Deployment: quando l'aumento <strong>di</strong> utilizzatori richiede una revisione delle<br />
politiche <strong>di</strong> <strong>di</strong>stribuzione ed incremento dei servizi a <strong>di</strong>sposizione dei client, alcuni<br />
componenti possono essere duplicati o spostati su altre macchine, ed il<br />
rein<strong>di</strong>rizzamento delle connessioni tra client-component e component-component<br />
può essere effettuato con una semplice mo<strong>di</strong>fica dei registri delle locazioni<br />
[Diagramma 4].<br />
Diagramma 4 : Flexible deployment
<strong>Architetture</strong> <strong>Concorrenti</strong> in prodotti a larga <strong>di</strong>ffusione - DCOM 15<br />
?? Dynamic Lo<strong>ad</strong> Balancing: la presenza <strong>di</strong> strumenti de<strong>di</strong>cati al rein<strong>di</strong>rizzamento<br />
trasparente delle richieste verso opportuni server (tramite i cosiddetti "referral<br />
component"), consente facilmente <strong>di</strong> bilanciare l'utilizzo delle risorse sul tale lato a<br />
seconda <strong>di</strong> parametri <strong>di</strong> carico e statistiche d'uso [Diagramma 5].<br />
Diagramma 5 : Referral component
<strong>Architetture</strong> <strong>Concorrenti</strong> in prodotti a larga <strong>di</strong>ffusione - DCOM 16<br />
2.2.2 Panoramica sull'architettura<br />
2.2.2.1 Connection Management<br />
COM consente <strong>ad</strong> un client <strong>di</strong> connettersi <strong>ad</strong> un server-component sulla stessa<br />
macchina <strong>di</strong>rettamente senza bisogno <strong>di</strong> passare attraverso componenti interme<strong>di</strong> ed<br />
interfacce proprietarie. L'estensione DCOM, dovendo provvedere al collegamento tra<br />
oggetti remoti, utilizza le stesse specifiche <strong>di</strong> COM, ma ne esegue l'instr<strong>ad</strong>amento<br />
verso l'oggetto desiderato in maniera trasparente all'utente, che o<strong>per</strong>a lo stesso tipo<br />
<strong>di</strong> chiamate che utilizzava nel caso non <strong>di</strong>stribuito [Diagramma 6].<br />
Diagramma 6 : Connessione tra componenti <strong>di</strong>stribuiti in DCOM<br />
DCOM non specifica un protocollo particolare <strong>per</strong> amministrare le comunicazioni via<br />
rete, cosa che richiederebbe un potenziale aggiornamento sul lato client verso la<br />
tecnologia prescelta, ma lascia libero ogni utente <strong>di</strong> mantenere il protocollo che ha a<br />
<strong>di</strong>sposizione implementando una politica <strong>di</strong> "Protocol Neutrality": DCOM costruisce<br />
un ulteriore livello sopra questi protocolli (TCP/IP, UDP,… ) ed amministra il tutto in<br />
maniera trasparente, aggiungendo, <strong>per</strong>ò, specifici controlli <strong>di</strong> sicurezza ed affidabilità<br />
della connessione.
<strong>Architetture</strong> <strong>Concorrenti</strong> in prodotti a larga <strong>di</strong>ffusione - DCOM 17<br />
Particolarmente interessanti, a questo riguardo, sono i meccanismi <strong>di</strong> pinging,<br />
mantenimento del numero <strong>di</strong> riferimenti, e la garbage collection <strong>di</strong>stribuita: quando un<br />
client richiede una connessione <strong>ad</strong> un server, il componente interessato, che può<br />
avere <strong>di</strong>verse comunicazioni già in corso con vari utilizzatori, incrementa un<br />
contatore che tiene traccia del numero <strong>di</strong> connessioni in corso; durante l'interazione<br />
client-component, un servizio <strong>di</strong> pinging controlla se il chiamante è ancora attivo sulla<br />
connessione e, in caso contrario (<strong>per</strong> problemi <strong>di</strong> rete o crash sul lato client), può<br />
decrementare il "reference count"; quando un componente rileva, dal contatore, <strong>di</strong><br />
non essere più referenziato da alcuno, può provvedere da solo alla liberazione delle<br />
risorse che occupava.<br />
2.2.2.2 Platform Neutrality<br />
DCOM è uno standard inter-piattaforma: consente ai componenti <strong>di</strong> non essere<br />
vincolati a macchine specifiche, ma <strong>di</strong> poter interagire con oggetti DCOM residenti in<br />
ambienti o<strong>per</strong>ativi eterogenei.<br />
L'approccio <strong>di</strong> DCOM alla "Platform Neutrality" è <strong>per</strong>ò molto <strong>di</strong>fferente da soluzioni<br />
<strong>ad</strong>ottate in altri sistemi platform-independent come Java.<br />
Una Java Virtual Machine (JVM) è un'astrazione <strong>di</strong> calcolatore, che fornisce<br />
un'interfaccia comune, in<strong>di</strong>pendente dalla macchina reale sottostante, e che<br />
consente a tutti gli applicativi Java <strong>di</strong> essere eseguiti nel medesimo tipo d'ambiente. I<br />
sorgenti Java sono quin<strong>di</strong> semi-compilati in un "bytecode" specifico delle JVM, che<br />
provvedono <strong>ad</strong> eseguire, in modalità interpretata, le loro istruzioni. Sebbene questo<br />
sistema consenta <strong>ad</strong> ogni bytecode, <strong>di</strong> essere riconosciuto da ogni JVM in<br />
esecuzione su qualsiasi tipo <strong>di</strong> macchina, le prestazioni <strong>di</strong> un linguaggio interpretato<br />
sono inferiori a quelle ottenibili da un co<strong>di</strong>ce compilato specificatamente <strong>per</strong> un dato<br />
sistema.<br />
DCOM, invece, offre uno standard binario <strong>per</strong>-platform: ogni utente può acquistare<br />
componenti specifici <strong>per</strong> il proprio ambiente, che vengono quin<strong>di</strong> ottimizzati, ma che<br />
possono comunicare con componenti <strong>di</strong> altre piattaforme in maniera standar<strong>di</strong>zzata.<br />
In aggiunta, viene assicurata l'intero<strong>per</strong>abilità con altri standard platform-neutral<br />
(quali Java) [Bibl. 8].
<strong>Architetture</strong> <strong>Concorrenti</strong> in prodotti a larga <strong>di</strong>ffusione - DCOM 18<br />
2.2.2.3 Remote Method Call: marshaling ed unmarshaling<br />
Quando un client vuole chiamare un oggetto che risiede in un <strong>di</strong>fferente<br />
<strong>ad</strong>dress space, il passaggio dei parametri d'invocazione, e l'eventuale restituzione<br />
dei risultati della computazione, avvengono tramite lo stack. Quando la chiamata<br />
intercorre tra entità che non con<strong>di</strong>vidono il medesimo stack, la parte <strong>di</strong> co<strong>di</strong>ce che<br />
amministra la Remote Procedure Call (RPC) deve occuparsi <strong>di</strong> leggere i parametri<br />
dallo stack e scriverli in un buffer <strong>di</strong> memoria, in modo che siano trasmissibili<br />
attraverso una connessione <strong>di</strong> rete: tale processo e detto "marshaling", mentre<br />
l'o<strong>per</strong>azione inversa, che deve avvenire sul lato server, è chiamata "unmarshaling";<br />
dopo che lo stack è stato ricostruito, allora l'oggetto può essere invocato.<br />
La procedura non è <strong>per</strong> niente banale. I parametri della chiamata possono contenere<br />
dati complessi come strutture, puntatori ed alberi <strong>di</strong> puntatori: il co<strong>di</strong>ce deve navigare<br />
tutti i no<strong>di</strong> e risolvere gli annidamenti.<br />
I dati così recu<strong>per</strong>ati devono avere, <strong>per</strong> poter essere trasmessi e poi ricreati<br />
correttamente, una rappresentazione ben definita e standar<strong>di</strong>zzata. DCOM si basa<br />
sullo standard DCE RPC (Distributed Computing Environment RPC), che utilizza<br />
NDR (Network Data Representation) <strong>per</strong> rappresentare i tipi <strong>di</strong> dati più <strong>di</strong>ffusi.
<strong>Architetture</strong> <strong>Concorrenti</strong> in prodotti a larga <strong>di</strong>ffusione - DCOM 19<br />
Per poter effettuare una chiamata <strong>ad</strong> un oggetto, è necessario conoscere l'insieme<br />
dei suoi meto<strong>di</strong>, completo <strong>di</strong> parametri d'ingresso ed uscita. La descrizione<br />
dell'interfaccia <strong>di</strong> un oggetto è fornita usando un apposito IDL (Interface Definition<br />
Language): un compilatore apposito, il Microsoft IDL (MIDL) compiler, fornisce,<br />
partendo dagli IDL, il co<strong>di</strong>ce C sorgente che contiene i meto<strong>di</strong> <strong>di</strong><br />
marshaling/unmarshaling separatamente <strong>per</strong> il client e <strong>per</strong> il server (tale co<strong>di</strong>ce è<br />
detto "proxy" e "stub" rispettivamente) [Diagramma 7].[Bibl. 9]<br />
Diagramma 7 : Comunicazione tra oggetti DCOM<br />
2.2.3 Modelli <strong>di</strong> concorrenza<br />
Non si può affermare che DCOM abbia un'architettura <strong>di</strong> threa<strong>di</strong>ng, in quanto utilizza<br />
i meccanismi forniti dal sistema sottostante, ma <strong>ad</strong>otta modelli <strong>per</strong> in<strong>di</strong>care come<br />
dovrebbero essere gestite le situazioni <strong>di</strong> concorrenza tra componenti.
<strong>Architetture</strong> <strong>Concorrenti</strong> in prodotti a larga <strong>di</strong>ffusione - DCOM 20<br />
A seconda del modello prescelto, DCOM garantisce un crescente livello <strong>di</strong> protezione<br />
od una maggiore flessibilità nell'<strong>ad</strong>attarsi agli scenari desiderati dal programmatore.<br />
Per offrire la dovuta sincronizzazione tra chiamate che avvengono tra oggetti in<br />
esecuzione su <strong>di</strong>fferenti thre<strong>ad</strong> della stessa macchina, il sistema <strong>ad</strong>o<strong>per</strong>a lo stesso<br />
metodo utilizzato <strong>per</strong> le RPC: il marshaling [Diagramma 8].<br />
Diagramma 8 : Invocazioni tra oggetti <strong>di</strong> thre<strong>ad</strong> <strong>di</strong>fferenti<br />
2.2.3.1 Single-Thre<strong>ad</strong>ed Apartment<br />
In questo modello (STA) ogni oggetto vive in un solo thre<strong>ad</strong> (esiste anche un'ulteriore<br />
specializzazione del modello (STA Main thre<strong>ad</strong> only) nel quale TUTTE le istanze<br />
sono create nello stesso thre<strong>ad</strong>).<br />
Valgono le seguenti regole:<br />
?? Dato un oggetto in un STA, se gli oggetti residenti in altri Apartment vogliono<br />
comunicare con esso, i due thre<strong>ad</strong> vengono sincronizzati tramite marshaling.<br />
?? Gli oggetti residenti nello stesso Apartment non sono sincronizzati tra loro.
<strong>Architetture</strong> <strong>Concorrenti</strong> in prodotti a larga <strong>di</strong>ffusione - DCOM 21<br />
?? Se due oggetti risiedono nello stesso Apartment, ed uno <strong>di</strong> essi sta processando<br />
una chiamata, nessun'altra invocazione può essere accettata dall'Apartment,<br />
anche se <strong>di</strong>retta al secondo componente [Diagramma 9].<br />
Diagramma 9 : Concorrenza tra invocazioni <strong>di</strong> oggetti in Apartment <strong>di</strong>fferenti<br />
2.2.3.2 MultiThre<strong>ad</strong>ed Apartment<br />
Un MTA è un modello libero <strong>di</strong> concorrenza: un oggetto non risiede in nessun thre<strong>ad</strong><br />
specifico, ed ogni oggetto all'interno dell'Apartment (unico <strong>per</strong> ogni singolo processo)<br />
può essere invocato <strong>di</strong>rettamente (senza bisogno <strong>di</strong> marshaling) da qualsiasi altro<br />
componente, anche se residente in altri thre<strong>ad</strong>.<br />
Naturalmente questo elimina gli overhe<strong>ad</strong>, ma demanda completamente al<br />
programmatore il controllo delle necessarie sincronizzazioni (tramite eventi, semafori<br />
o quant'altro).<br />
La maggiore libertà nella sincronizzazione consente, <strong>per</strong>ò, <strong>di</strong> poter <strong>ad</strong>attare al meglio<br />
la gestione delle "sezioni critiche", così da ottenere un "fine tuning" dell'applicazione<br />
(utilizzando costrutti quali i lock manuali).
<strong>Architetture</strong> <strong>Concorrenti</strong> in prodotti a larga <strong>di</strong>ffusione - CORBA 22<br />
2.2.3.3 Free-Thre<strong>ad</strong>ed Marshaler<br />
Ogni invocazione tra componenti residenti in <strong>di</strong>fferenti Apartment richiede un<br />
processo <strong>di</strong> marshaling, e questo è molto più <strong>di</strong>spen<strong>di</strong>oso <strong>di</strong> una chiamata <strong>di</strong>retta.<br />
DCOM mette, <strong>per</strong>ò, a <strong>di</strong>sposizione del programmatore gli strumenti <strong>per</strong><br />
<strong>per</strong>sonalizzare ogni processo <strong>di</strong> marshaling: sfruttando questa possibilità, lo<br />
sviluppatore ha la facoltà <strong>di</strong> agire anche sul "marshaling inter-thre<strong>ad</strong>", e quin<strong>di</strong>, <strong>di</strong><br />
aggirare le o<strong>per</strong>azioni <strong>di</strong> esternalizzazione e passare <strong>di</strong>rettamente un riferimento al<br />
client invocante [Bibl. 9].<br />
2.3 CORBA<br />
CORBA, acronimo <strong>per</strong> Common Object Request Broker Architecture, è un insieme <strong>di</strong><br />
specifiche prodotto dalla OMG (Object Management Group) ed in collaborazione con<br />
la X/Open. Queste organizzazioni hanno lo scopo <strong>di</strong> standar<strong>di</strong>zzare ed in<strong>di</strong>rizzare, a<br />
livello globale, le nuove tecniche <strong>di</strong> programmazione orientata agli oggetti (OOP), in<br />
maniera in<strong>di</strong>pendente da prodotti <strong>di</strong> specifici fornitori e <strong>di</strong> darne un'implementazione<br />
"a<strong>per</strong>ta"; le specifiche in questione riguardano la standar<strong>di</strong>zzazione <strong>di</strong> un ORB.<br />
2.3.1 Introduzione<br />
Un ORB è un oggetto software il cui scopo è rendere possibile le comunicazioni ed il<br />
mutuo utilizzo tra applicazioni informatiche che si trovano <strong>di</strong>stribuite su una rete<br />
eterogenea <strong>di</strong> macchine (<strong>di</strong>versi fornitori dell'applicativo, <strong>di</strong>fferenti sistemi o<strong>per</strong>ativi,<br />
linguaggi <strong>di</strong> programmazione e <strong>per</strong>sino tecnologie <strong>di</strong> rete).<br />
Data l'integrazione che un tale sistema promette <strong>di</strong> fornire, i vantaggi <strong>di</strong> questa<br />
tecnologia risultano maggiormente evidenti in realtà complesse che devono <strong>di</strong>alogare<br />
con molti utenti <strong>di</strong>fferenti: un ORB è il middleware <strong>di</strong> riferimento <strong>di</strong> molti server che<br />
o<strong>per</strong>ano in Internet usando il modello client-server.<br />
Il livello d'integrazione platform-independent viene ottenuto tramite due meccanismi<br />
chiave:
<strong>Architetture</strong> <strong>Concorrenti</strong> in prodotti a larga <strong>di</strong>ffusione - CORBA 23<br />
1. La scelta <strong>di</strong> un linguaggio unico <strong>per</strong> specificare le interfacce degli oggetti coinvolti<br />
nell'interazione in rete: in modo che sia il client, sia il server sappiano,<br />
univocamente e senza ambiguità <strong>di</strong> sorta, quali richieste possono essere invocate<br />
sull'oggetto.<br />
2. La scelta <strong>di</strong> un protocollo comune <strong>per</strong> effettuare le comunicazioni vere e proprie<br />
attraverso una generica rete.<br />
CORBA si fonda su IDL (Interface Definition Language) anch'esso definito da OMG<br />
<strong>per</strong> <strong>di</strong>sciplinare la descrizione delle interfacce (con i loro meto<strong>di</strong>, parametri, tipi <strong>di</strong> dati<br />
<strong>di</strong> ritorno, etc.), e propone IIOP (Internet Inter-ORB Protocol) come protocollo <strong>di</strong><br />
comunicazione.<br />
Si deve ricordare che CORBA è unicamente un insieme <strong>di</strong> specifiche su come deve<br />
o<strong>per</strong>are e quali funzioni deve fornire un ORB che voglia <strong>di</strong>rsi "CORBA compliant":<br />
non si <strong>di</strong>ce come tali funzionalità debbano essere implementate, ma unicamente<br />
come debbano comportarsi. Nulla <strong>di</strong> implementativo è stato sviluppato da OMG, ma<br />
<strong>di</strong>versi produttori <strong>di</strong> software hanno scelto <strong>di</strong> fornire applicativi conformi a tale<br />
standard; limitandosi, <strong>per</strong>ò, CORBA alle specifiche d'interfaccia, ogni ORB sviluppato<br />
partendo da queste è libero <strong>di</strong> effettuare le scelte implementative volute, ed è quin<strong>di</strong><br />
unico <strong>per</strong> prestazioni, flessibilità, robustezza, scalabilità, etc [Bibl. 12].<br />
2.3.2 Panoramica sull'architettura<br />
Un programmatore che volesse <strong>ad</strong>o<strong>per</strong>are CORBA deve definire l'interfaccia <strong>di</strong> ogni<br />
oggetto facente parte dell'interazione sulla rete usando IDL in modo che non vi siano<br />
ambiguità sulle sue possibilità. La separazione tra definizione ed implementazione<br />
dell'oggetto è il punto <strong>di</strong> forza <strong>di</strong> CORBA, poiché lascia liberi <strong>di</strong> <strong>ad</strong>o<strong>per</strong>are, in fase <strong>di</strong><br />
scrittura dei meto<strong>di</strong>, qualsiasi linguaggio prescelto dal programmatore: uno specifico<br />
compilatore IDL si occupa, infatti, in una fase successiva, <strong>di</strong> mappare la definizione<br />
in due oggetti language-specific separati. Gli oggetti sono detti "stub" e "skeleton". Gli<br />
stub servono da proxy <strong>per</strong> le chiamate da parte <strong>di</strong> client, mentre gli skeleton<br />
implementano i proxy del lato server. Il progettista, una volta ottenuto lo skeleton<br />
della classe, contenente tutte le definizioni dei meto<strong>di</strong> precisate in fase <strong>di</strong> scrittura<br />
IDL, non deve fare altro che estenderlo implementando le chiamate ai meto<strong>di</strong>: stub e
<strong>Architetture</strong> <strong>Concorrenti</strong> in prodotti a larga <strong>di</strong>ffusione - CORBA 24<br />
skeleton, che possono essere anche molto voluminosi, incapsulano tutte le<br />
procedure <strong>di</strong> necessarie <strong>per</strong> le chiamate remote e la conversione delle richieste in<br />
dati trasmissibili.<br />
Il client, che pensa <strong>di</strong> chiamare un metodo <strong>di</strong> un dato oggetto, in realtà chiama il<br />
corrispondente metodo dello stub. Questo passa la comunicazione al lato server<br />
(attraverso l'ORB), ed infine la richiesta subisce un <strong>di</strong>spatch verso lo skeleton e la<br />
sua concretizzazione [Diagramma 10] [Bibl. 11]<br />
Diagramma 10 : Meccanismo d'invocazione dei meto<strong>di</strong> in CORBA<br />
2.3.3 Specifiche <strong>per</strong> la concorrenza<br />
CORBA, come già accennato, non entra nell'ambito delle specifiche implementative<br />
dell'ORB e quin<strong>di</strong>, <strong>per</strong> quanto riguarda le caratteristiche legate a meccanismi<br />
d'esecuzione, come sono quelle relative alla concorrenza, resta spesso volutamente<br />
nel vago.<br />
Per quanto concerne il lato server dell'applicazione, vengono comunque delineati dei<br />
blocchi e servizi <strong>di</strong> massima che devono essere forniti, e si è pensato un
<strong>Architetture</strong> <strong>Concorrenti</strong> in prodotti a larga <strong>di</strong>ffusione - CORBA 25<br />
meccanismo <strong>per</strong> interfacciare l'effettiva esecuzione delle richieste e specificare alcuni<br />
parametri fondamentali <strong>di</strong> tale comportamento.<br />
L'oggetto principale demandato all'attuazione delle esecuzioni in CORBA è il<br />
"servant": un servant, visibile solo dal lato server, è l'insieme <strong>di</strong> CPU, memoria e<br />
risorse necessarie <strong>per</strong> l'esecuzione <strong>di</strong> un'o<strong>per</strong>azione invocata. Un oggetto CORBA<br />
può, infatti, essere creato e <strong>di</strong>strutto e queste o<strong>per</strong>azioni delimitano il suo "lifetime"<br />
come visibile sia dal lato client sia da quello server: unicamente durante il lifetime <strong>di</strong><br />
un oggetto un client può invocare su <strong>di</strong> esso delle o<strong>per</strong>azioni, ma, <strong>per</strong> quanto<br />
concerne la visione del lato server, un oggetto può assumere altri stati specifici<br />
mentre risulta "presente". Per gestire meglio le risorse del server, infatti, sarebbe<br />
sconveniente che tutte le implementazioni degli oggetti necessari occupassero CPU<br />
e memoria contemporaneamente, ma sarebbe auspicabile una loro assegnazione<br />
solo quando realmente necessaria: <strong>per</strong> questo un oggetto, sul lato server, possiede<br />
anche le caratteristiche <strong>di</strong> essere "attivo" o "sospeso". Il modulo che si occupa <strong>di</strong><br />
queste variazioni nello stato delle implementazioni è detto POA (Portable Object<br />
Adapter) [Bibl. 10].
<strong>Architetture</strong> <strong>Concorrenti</strong> in prodotti a larga <strong>di</strong>ffusione - CORBA 26<br />
Un POA si occupa del risveglio-sospensione <strong>di</strong> un oggetto e della gestione delle<br />
assegnazioni dei servant <strong>ad</strong> essi a tempo d'esecuzione: è l'amministratore delle<br />
politiche <strong>di</strong> scalabilità <strong>di</strong> un ORB; quando un client invoca una chiamata <strong>ad</strong> un<br />
oggetto server, questa viene filtrata dal POA che provvede, attenendosi alle politiche<br />
assegnate, alla sua effettiva esecuzione [Diagramma 11].<br />
Diagramma 11 : Funzionamento del POA<br />
Il POA è completamente <strong>di</strong>pendente dall'implementazione, ma CORBA ha<br />
specificato alcune caratteristiche che deve possedere ed i mo<strong>di</strong> <strong>per</strong> assegnargliele:<br />
l'oggetto Policy è de<strong>di</strong>cato a questo scopo e ne esistono <strong>di</strong>fferenti specializzazioni<br />
(sette <strong>per</strong> la precisione), tra cui anche quelle relative ai modelli <strong>di</strong> threa<strong>di</strong>ng quando<br />
un ORB è implementato in maniera multi-thre<strong>ad</strong>ed (e relative politiche <strong>di</strong><br />
concorrenza).<br />
CORBA prevede tre <strong>di</strong>fferenti "threa<strong>di</strong>ng model" incapsulati nell'oggetto<br />
Thre<strong>ad</strong>Policy:
<strong>Architetture</strong> <strong>Concorrenti</strong> in prodotti a larga <strong>di</strong>ffusione - CORBA 27<br />
1. Single Thre<strong>ad</strong>: ogni chiamata eseguita dal singolo POA ai servant deve essere<br />
processata sequenzialmente. Un POA può avere chiamate rientranti, ma<br />
l'esecuzione delle stesse non deve avvenire in maniera concorrente.<br />
2. ORB Controlled: la gestione dei thre<strong>ad</strong> è demandata totalmente<br />
all'implementazione dell'ORB.<br />
3. Main Thre<strong>ad</strong>: tutte le richieste demandate a POA che <strong>ad</strong>ottano questa politica<br />
sono eseguite sequenzialmente.<br />
La prima e l'ultima <strong>di</strong> queste politiche sono espressamente pensate <strong>per</strong><br />
salvaguardare l'esecuzione <strong>di</strong> co<strong>di</strong>ce "multi-thre<strong>ad</strong>-unaware".<br />
Le specifiche <strong>di</strong> CORBA <strong>per</strong> la concorrenza sono interamente contenute in quello<br />
che si è presentato: <strong>per</strong> avere qu<strong>ad</strong>ro più approfon<strong>di</strong>to è necessario esaminare le<br />
scelte implementative dei singoli applicativi "CORBA compliant" [Bibl. 13].<br />
2.3.4 Un esempio d'implementazione: ORBacus<br />
ORBacus è un ORB, commercializzato dalla IONA Technologies, compatibile con le<br />
specifiche CORBA in generale e che si rifà <strong>ad</strong> un modello implementativo sviluppato<br />
in C++ ed in Java (risulta quin<strong>di</strong> compatibile con i documenti "C++ Language<br />
mapping" e "IDL/Java Language mapping" <strong>di</strong> OMG).<br />
Essendo un'implementazione, fornisce maggiori specifiche <strong>per</strong> quanto riguarda il<br />
modello <strong>di</strong> concorrenza ed offre allo sviluppatore software <strong>di</strong>verse opportunità <strong>per</strong><br />
rispondere meglio alle esigenze specifiche delle applicazioni che s'intendono creare.<br />
ORBacus <strong>di</strong>vide i modelli che l'utente può <strong>ad</strong>o<strong>per</strong>are, <strong>per</strong> definire come l'ORB<br />
gestisce le comunicazioni e le richieste d'esecuzione, in due categorie principali,<br />
Single-thre<strong>ad</strong>ed e Multi-thre<strong>ad</strong>ed, e lascia l'ulteriore gr<strong>ad</strong>o <strong>di</strong> libertà <strong>di</strong> separarle <strong>per</strong><br />
le implementazioni client e <strong>per</strong> quelle sul lato server. I modelli sono Blocking,<br />
Reactive e Thre<strong>ad</strong>ed <strong>per</strong> il lato client e Reactive, Thre<strong>ad</strong>ed, Thre<strong>ad</strong>-<strong>per</strong>-Client,<br />
Thre<strong>ad</strong>-<strong>per</strong>-Request e Thre<strong>ad</strong> Pool <strong>per</strong> quello server; i para<strong>di</strong>gmi Blocking e<br />
Reactive rientrano nella categoria dei Single-thre<strong>ad</strong>ed, mentre i restanti in quella<br />
Multi-thre<strong>ad</strong>ed.<br />
La politica Blocking è la più semplice: implica che l'ORB si blocca mentre il client<br />
manda una richiesta (nel frattempo non è possibile eseguire altre o<strong>per</strong>azioni).
<strong>Architetture</strong> <strong>Concorrenti</strong> in prodotti a larga <strong>di</strong>ffusione - CORBA 28<br />
Il modello Reactive <strong>per</strong>mette, a client e server, <strong>di</strong> mandare/ricevere richieste mentre<br />
stanno aspettando il ritorno <strong>di</strong> (o eseguendo nel caso del server) altre chiamate.<br />
I modelli Multi-thre<strong>ad</strong>ed sono più interessanti <strong>per</strong> gli ambiti <strong>di</strong> questa trattazione e<br />
verranno esaminati più approfon<strong>di</strong>tamente.<br />
2.3.4.1 Modello Thre<strong>ad</strong>ed<br />
In questo modello ogni client (server) utilizza due thre<strong>ad</strong> separati <strong>per</strong> ogni<br />
connessione con un <strong>di</strong>verso server (client): un thre<strong>ad</strong> è utilizzato <strong>per</strong> l'invio (<strong>di</strong><br />
richieste) e l'altro <strong>per</strong> la ricezione. Il server, inoltre, possiede un terzo thre<strong>ad</strong> <strong>per</strong><br />
l'accettazione delle richieste <strong>di</strong> connessione.<br />
Il server, anche se può ricevere più richieste simultaneamente, è <strong>per</strong>ò limitato a<br />
doverle eseguire in modo serializzato (e quin<strong>di</strong> a bufferizzare e ritardare quelle che<br />
giungono durante l'esecuzione <strong>di</strong> precedenti chiamate): il modello consente un solo<br />
thre<strong>ad</strong> attivo <strong>per</strong> tutto il co<strong>di</strong>ce utente (server o client) [Diagramma 12]. Questa<br />
accortezza <strong>per</strong>mette al co<strong>di</strong>ce <strong>di</strong> non doversi preoccupare <strong>di</strong> meccanismi <strong>di</strong><br />
sincronizzazione delle invocazioni ai meto<strong>di</strong>.<br />
Diagramma 12 : Thre<strong>ad</strong>ed server
<strong>Architetture</strong> <strong>Concorrenti</strong> in prodotti a larga <strong>di</strong>ffusione - CORBA 29<br />
2.3.4.2 Modello Thre<strong>ad</strong>-<strong>per</strong>-Client<br />
Del tutto simile al modello precedente, si <strong>di</strong>fferenzia <strong>per</strong> il fatto che ora è consentito<br />
un thre<strong>ad</strong> attivo alla volta <strong>per</strong> ogni <strong>di</strong>stinto client: o<strong>per</strong>azioni richieste da client <strong>di</strong>versi<br />
possono essere eseguite parallelamente (raffinando quin<strong>di</strong> la grana della<br />
concorrenza) [Diagramma 13]. Ancora una volta, <strong>per</strong>ò, se più chiamate arrivano quasi<br />
simultaneamente dallo stesso client (o comunque mentre una precedente o<strong>per</strong>azione<br />
richiesta dallo stesso client deve ancora essere portata a termine), l'esecuzione della<br />
seconda invocazione viene <strong>di</strong>fferita.<br />
Diagramma 13 : Thre<strong>ad</strong>-<strong>per</strong>-Client server
<strong>Architetture</strong> <strong>Concorrenti</strong> in prodotti a larga <strong>di</strong>ffusione - CORBA 30<br />
2.3.4.3 Modello Thre<strong>ad</strong>-<strong>per</strong>-Request<br />
L'ORB che utilizza tale modello crea un thre<strong>ad</strong> d'esecuzione <strong>per</strong> ogni richiesta che gli<br />
<strong>per</strong>viene consentendo che nessuna richiesta venga ritardata. Il modello è<br />
particolarmente efficace in quei sistemi in cui il tempo d'esecuzione risulta critico e<br />
<strong>di</strong>viene prioritario servire ogni richiesta imme<strong>di</strong>atamente, ma risulta particolarmente<br />
inefficiente <strong>per</strong> lo spreco <strong>di</strong> risorse e l'overhe<strong>ad</strong> insito nelle o<strong>per</strong>azioni <strong>di</strong> creazione<br />
dei nuovi thre<strong>ad</strong> [Diagramma 14].<br />
Diagramma 14 : Thre<strong>ad</strong>-<strong>per</strong>-Request server
<strong>Architetture</strong> <strong>Concorrenti</strong> in prodotti a larga <strong>di</strong>ffusione - EJB 31<br />
2.3.4.4 Modello Thre<strong>ad</strong> Pool<br />
Un compromesso tra ritar<strong>di</strong> nella risposta alle chiamate ed efficienza nell'utilizzo delle<br />
risorse, può essere ottenuto servendosi <strong>di</strong> una collezione <strong>di</strong> thre<strong>ad</strong> creati una volta<br />
<strong>per</strong> tutte in fase <strong>di</strong> inizializzazione e riutilizzati ogni volta che ve ne sia bisogno. Non<br />
vi è così <strong>per</strong><strong>di</strong>ta <strong>di</strong> tempo <strong>per</strong> le o<strong>per</strong>azioni <strong>di</strong> creazione dei thre<strong>ad</strong> e, inoltre, le<br />
richieste dovranno essere ritardate solo nell'evenienza che tutto il pool sia<br />
completamente occupato nell'istante del loro inoltro [Diagramma 15] [Bibl. 14].<br />
Diagramma 15 : Thre<strong>ad</strong> Pool server<br />
2.4 EJB<br />
Enterprise JavaBeans è una specifica API (Application Programming Interface),<br />
prodotta da Sun Microsystems, che estende il modello a componenti <strong>di</strong> JavaBeans<br />
mettendo a <strong>di</strong>sposizione degli sviluppatori d'applicativi <strong>per</strong> le imprese, un ambiente<br />
Object-Oriented transazionale.
<strong>Architetture</strong> <strong>Concorrenti</strong> in prodotti a larga <strong>di</strong>ffusione - EJB 32<br />
2.4.1 Introduzione<br />
2.4.1.1 JavaBeans<br />
JavaBeans è un'estensione <strong>di</strong> Java nata con lo scopo <strong>di</strong> creare un modello <strong>di</strong><br />
componenti, finalizzato all'utilizzo in ambienti <strong>per</strong> lo sviluppo d'applicazioni (IDEs) <strong>di</strong><br />
tipo "visuale". I Bean sono stati pensati <strong>per</strong> essere riusabili.<br />
Supportano caratteristiche quali:<br />
1. Introspezione: capacità, da parte dell'IDE, <strong>di</strong> analizzare come il componente<br />
o<strong>per</strong>a.<br />
2. Customizzazione: possibilità, <strong>ad</strong> o<strong>per</strong>a dello sviluppatore, <strong>di</strong> <strong>ad</strong>attare facilmente il<br />
componente alle proprie esigenze.<br />
3. Gestione degli eventi: consente <strong>ad</strong> un Bean <strong>di</strong> interagire con gli altri componenti<br />
in maniera reattiva entrando a far parte della rete <strong>di</strong> connessioni.<br />
I componenti, essendo scritti utilizzando Java, sono portabili su <strong>di</strong>verse piattaforme e<br />
vengono incontro all'esigenza, sempre più sentita dalle case produttrici <strong>di</strong> software, <strong>di</strong><br />
fornire ai clienti moduli componibili secondo le specifiche esigenze, piuttosto che<br />
applicazioni monolitiche [Bibl. 15].<br />
2.4.1.2 Ambienti transazionali<br />
Col termine <strong>di</strong> "transazione" s'intende l'insieme <strong>di</strong> una o più o<strong>per</strong>azioni che<br />
costituiscono un'unità d'azione logicamente inscin<strong>di</strong>bile. Essendo in<strong>di</strong>visibile,<br />
l'insieme delle o<strong>per</strong>azioni che la compongono deve essere eseguito nella sua<br />
interezza <strong>per</strong> poter affermare che l'o<strong>per</strong>azione è andata a buon fine, altrimenti tutta la<br />
transazione fallisce e <strong>di</strong>venta necessario ripristinare lo stato precedente le mo<strong>di</strong>fiche<br />
già avvenute: una transazione può cominciare, eseguire alcuni passi del processo,<br />
ma, nel caso dovessero intervenire con<strong>di</strong>zioni <strong>per</strong> le quali fosse impossibile<br />
proseguire nello svolgimento, potrebbe poi fallire e dover, letteralmente, "tornare sui<br />
suoi passi". Tale processo <strong>di</strong> recu<strong>per</strong>o è detto <strong>di</strong> "rollback".<br />
Nella vita <strong>di</strong> tutti i giorni si è spesso in presenza <strong>di</strong> o<strong>per</strong>azioni transazionali: la spesa<br />
in un negozio (si deve scegliere il prodotto, pagarlo e portarlo con se fuori della<br />
bottega), un prelievo <strong>di</strong> contante <strong>ad</strong> uno sportello bancario automatico, etc.
<strong>Architetture</strong> <strong>Concorrenti</strong> in prodotti a larga <strong>di</strong>ffusione - EJB 33<br />
Certamente non si desidera che, dopo aver pagato un bene, il commerciante<br />
consideri conclusa l'o<strong>per</strong>azione, o almeno, se non vuole darci ciò che abbiamo<br />
pagato, dovremmo avere in<strong>di</strong>etro il denaro.<br />
Negli ambienti informatici, specialmente quelli legati <strong>ad</strong> ambiti commerciali in cui<br />
siano presenti numerosi contatti con <strong>di</strong>versi clienti contemporaneamente (quali<br />
banche o esercizi commerciali on-line, agenzie <strong>per</strong> la prenotazione <strong>di</strong> posti in treno o<br />
aereo, etc.), il problema delle transazioni è quin<strong>di</strong> centrale e richiede particolare<br />
attenzione.<br />
Non sempre, <strong>per</strong>ò, il programmatore vuole avere a che fare con tale livello funzionale<br />
e spesso sarebbe preferibile una concentrazione <strong>di</strong> risorse sui contenuti piuttosto che<br />
sugli strumenti: è in questo scenario che nasce l'esigenza <strong>di</strong> prodotti middleware<br />
quali sono quelli compatibili con le specifiche <strong>di</strong> EJB.<br />
2.4.2 Panoramica sull'architettura<br />
Diagramma 16 : Scenario dell'architettura <strong>di</strong> EJB
<strong>Architetture</strong> <strong>Concorrenti</strong> in prodotti a larga <strong>di</strong>ffusione - EJB 34<br />
EJB si basa sulla descrizione <strong>di</strong> alcuni componenti e delle interfacce che ne regolano<br />
il comportamento. Due sono gli attori principali dello scenario:<br />
1. Gli entity Bean<br />
2. I session Bean<br />
Un entity Bean non è altro che un oggetto (nel senso informatico del termine) dotato<br />
<strong>di</strong> alcune proprietà caratterizzanti:<br />
1. È <strong>per</strong>manente: mentre un comune oggetto cessa <strong>di</strong> esistere quando il programma<br />
che lo ha creato termina l'esecuzione, un entity Bean continua <strong>ad</strong> esistere fino a<br />
che non viene cancellato (o il database su cui è memorizzato il suo stato subisce<br />
danni irreversibili). Uno stesso oggetto può continuare le sue attività tra <strong>di</strong>verse<br />
sessioni d'attivazione del programma (è a prova <strong>di</strong> crash del Container che lo<br />
amministra).<br />
2. È basato sulla rete: l'entity Bean è stato pensato <strong>per</strong> l'utilizzo con<strong>di</strong>viso in rete.<br />
Esistono, quin<strong>di</strong>, utilità <strong>per</strong> la ricerca ed il recu<strong>per</strong>o <strong>di</strong> Bean <strong>per</strong> il loro successivo<br />
utilizzo.<br />
3. È eseguito in remoto: i meto<strong>di</strong> <strong>di</strong> un entity Bean sono fisicamente eseguiti su un<br />
server EJB (e possono essere invocati da client remoti).<br />
4. È identificato da una "primary key": a ciascun oggetto è associato un identificatore<br />
unico <strong>per</strong> ogni istanza.<br />
I session Bean sono, invece, entità utilizzate <strong>per</strong> eseguire un compito specifico <strong>per</strong><br />
conto d un singolo client: non sono <strong>per</strong>manenti, ma vengono creati all'occorrenza <strong>per</strong><br />
isolare i sotto-compiti specifici <strong>di</strong> un'o<strong>per</strong>azione.<br />
Esistono le specifiche anche <strong>per</strong> le interfacce associate <strong>ad</strong> un Bean.<br />
Queste sono:<br />
1. La "Home Interface": usata dai client <strong>per</strong> creare o trovare istanze <strong>di</strong> un EJB.<br />
2. La "Remote Interface": rappresenta le o<strong>per</strong>azioni messe a <strong>di</strong>sposizione dal Bean<br />
e necessita <strong>di</strong> un'implementazione sul lato server dell'applicazione.<br />
EJB specifica come le istanze dei Bean debbano essere accessibili e come il server<br />
debba gestirle. Il contesto <strong>di</strong> un Bean è il suo Container: entità facente parte del<br />
server che amministra l'esecuzione degli EJB [Diagramma 16] [Bibl. 17].
<strong>Architetture</strong> <strong>Concorrenti</strong> in prodotti a larga <strong>di</strong>ffusione - Confronti 35<br />
2.4.3 Specifiche <strong>per</strong> la concorrenza<br />
EJB non entra nel merito dell'interfaccia Container-server (il cui confine non è<br />
neanche eccessivamente delimitato), ma si concentra particolarmente sulle<br />
specifiche <strong>per</strong> quella Container-Bean: <strong>per</strong> questo motivo l'amministrazione delle<br />
risorse è nascosta, ed o<strong>per</strong>azioni come la gestione delle connessioni ai database,<br />
quella della memoria e dei thre<strong>ad</strong> sono demandati interamente al server (<strong>di</strong>pendente<br />
dal fornitore). Un server EJB provvede a tutta l'infrastruttura necessaria <strong>per</strong><br />
l'esecuzione dei Container.<br />
In EJB non esistono, quin<strong>di</strong>, specifiche riguardanti le politiche <strong>di</strong> concorrenza da far<br />
<strong>ad</strong>ottare <strong>ad</strong> un server, ma alcune limitazioni concernenti vengono esplicitamente<br />
imposte alle implementazioni dei Bean <strong>per</strong> evitare l'insorgere <strong>di</strong> problemi <strong>di</strong><br />
sicurezza, <strong>di</strong> quelli tipici del multi-threa<strong>di</strong>ng e favorire il controllo da parte dei<br />
Container:<br />
1. Un Container deve assicurare che al più un thre<strong>ad</strong> possa eseguire una data<br />
istanza <strong>per</strong> volta: se, mentre un'implementazione è in esecuzione, giunge una<br />
seconda richiesta, da parte <strong>di</strong> un client, sulla stessa istanza, questa deve essere<br />
rifiutata e bisogna lanciare un'opportuna eccezione.<br />
2. Un Bean non deve <strong>ad</strong>ottare le primitive <strong>di</strong> sincronizzazione <strong>di</strong> Java; in particolare<br />
non deve mai contenere la parola chiave "synchronized". Alcuni Container<br />
potrebbero infatti utilizzare più <strong>di</strong> una JVM (Java Virtual Machine) <strong>per</strong> eseguire<br />
<strong>di</strong>verse istanze invalidando la sincronizzazione.<br />
3. Un Bean non deve mai cercare <strong>di</strong> amministrare thre<strong>ad</strong>: non può invocare primitive<br />
quali stop(), suspend() e resume(), né gestire priorità o gruppi <strong>di</strong> thre<strong>ad</strong>. Queste<br />
funzioni sono <strong>di</strong> competenza del Container che ne deve avere il controllo assoluto<br />
<strong>per</strong> l'appropriata gestione dell'ambiente [Bibl. 18]<br />
2.5 Confronti<br />
Tra i prodotti presentati, quelli più facilmente confrontabili sono DCOM e CORBA.
<strong>Architetture</strong> <strong>Concorrenti</strong> in prodotti a larga <strong>di</strong>ffusione - Confronti 36<br />
EJB, mentre i suddetti sono orientati principalmente alla collaborazione ed offrono<br />
pochi servizi supplementari, è un prodotto che si colloca <strong>ad</strong> un livello su<strong>per</strong>iore. È<br />
stato pensato <strong>per</strong> sod<strong>di</strong>sfare sì esigenze d'integrazione ed intero<strong>per</strong>abilità tra<br />
ambienti ed applicativi eterogenei, ma con finalità particolari (attenzione alle<br />
transazioni) e relativi servizi de<strong>di</strong>cati.<br />
Un'ulteriore precisazione andrebbe fatta <strong>per</strong> JADE [cap. 3 JADE] che è un<br />
framework: oltre alle specifiche implementative viene fornito all'utente un completo<br />
ambiente d'esecuzione <strong>per</strong> le applicazioni sviluppate (nel caso in questione, la<br />
piattaforma <strong>per</strong> gli Agenti).<br />
Su<strong>per</strong>ate queste <strong>di</strong>fferenze, tutte le tecnologie presentate hanno <strong>per</strong>ò in comune la<br />
necessità <strong>di</strong> amministrare, efficacemente ed in maniera sicura, le risorse <strong>di</strong><br />
macchina, ed in particolare i thre<strong>ad</strong>: su questo piano comune possono essere quin<strong>di</strong><br />
fatte <strong>di</strong>verse considerazioni [Bibl. 21].<br />
2.5.1 DCOM e CORBA: creare l'infrastruttura <strong>per</strong> la comunicazione<br />
Sia DCOM che CORBA si prefiggono l'obiettivo <strong>di</strong> gestire, in maniera trasparente <strong>per</strong><br />
l'utente, le chiamate a meto<strong>di</strong> <strong>di</strong> oggetti <strong>di</strong>stribuiti, ma molte sono le <strong>di</strong>fferenze che<br />
separano i due standard.<br />
Innanzi tutto, mentre DCOM è nato come estensione <strong>di</strong> un meccanismo proprietario<br />
<strong>di</strong> Microsoft <strong>per</strong> la con<strong>di</strong>visione <strong>di</strong> oggetti intra-piattaforma (Object Linking and<br />
Embed<strong>di</strong>ng od OLE), e quin<strong>di</strong> possiede già tecnologie implementative consolidate,<br />
CORBA è semplicemente un insieme <strong>di</strong> specifiche e lascia liberi i produttori <strong>di</strong><br />
middleware <strong>di</strong> implementarle come meglio credono. Questo fatto, come<br />
effettivamente acc<strong>ad</strong>uto nei tempi imme<strong>di</strong>atamente successivi al rilascio <strong>di</strong> CORBA,<br />
può creare problemi <strong>di</strong> "intero<strong>per</strong>abilità" tra i prodotti <strong>di</strong> <strong>di</strong>versi fornitori, anche se<br />
d'altro canto, consente, essendo uno standard largamente supportato, una maggiore<br />
concorrenza ed offerta <strong>di</strong> prodotti. CORBA, inoltre, come tutte le specifiche <strong>di</strong> OMG,<br />
nasce da un processo "open" <strong>di</strong> creazione ed approvazione delle specifiche, che<br />
garantisce il contributo <strong>di</strong> <strong>di</strong>versi specialisti e quin<strong>di</strong> maggiore risposta <strong>ad</strong> esigenze<br />
<strong>di</strong>ffuse e continuità nello sviluppo (DCOM, al contrario, ha subito nel tempo bruschi<br />
cambiamenti dettati da strategie <strong>di</strong> marketing <strong>di</strong> Microsoft).
<strong>Architetture</strong> <strong>Concorrenti</strong> in prodotti a larga <strong>di</strong>ffusione - Confronti 37<br />
Numerose <strong>di</strong>fferenze nascono, poi, dalla scelta del livello su cui garantire<br />
l'intero<strong>per</strong>abilità: CORBA o<strong>per</strong>a su una compatibilità d'interfaccia, DCOM la richiede<br />
a livello <strong>di</strong> struttura del co<strong>di</strong>ce. Queste scelte comportano che:<br />
1. Il co<strong>di</strong>ce <strong>di</strong> oggetti DCOM, essendo ottimizzato <strong>per</strong> ogni piattaforma, dovrebbe<br />
essere più <strong>per</strong>formante (fonte Microsoft), e certamente lo è rispetto <strong>ad</strong> altre<br />
implementazioni in Java, ma si ricorda che CORBA possiede un'ampia serie <strong>di</strong><br />
specifiche <strong>per</strong> il mapping in vari linguaggi, <strong>ad</strong> esempio Java, ma anche C++, etc.,<br />
e tale scelta con<strong>di</strong>ziona le prestazioni ottenibili.<br />
2. CORBA è totalmente "platform-independent". Anche se DCOM <strong>di</strong>chiara lo stesso,<br />
il suo forte legame con le piattaforme, lo vincola a strumenti specifici <strong>per</strong> ogni<br />
macchina, il che può portare a <strong>di</strong>fferenze tra lo "stato dell'arte" su una piattaforma<br />
rispetto <strong>ad</strong> un'altra (nelle prime fasi del suo sviluppo, DCOM era fortemente<br />
supportato solo da ambienti Windows 9x ed NT).<br />
3. DCOM, essendo stato <strong>di</strong>ffuso insieme alle piattaforme Windows, è largamente<br />
<strong>di</strong>ffuso e non richiede l'acquisto <strong>di</strong> strumenti specifici. Il carico che un utente deve<br />
affrontare <strong>per</strong> utilizzare i componenti DCOM (essendo questi <strong>di</strong>stribuiti in forma <strong>di</strong><br />
co<strong>di</strong>ce specifico <strong>per</strong>-platform) è sicuramente minore <strong>di</strong> quello richiesto <strong>per</strong><br />
CORBA, che necessita dell'acquisizione <strong>di</strong> un'implementazione <strong>di</strong> ORB.<br />
Da un punto <strong>di</strong> vista più tecnico si può notare, infine, come CORBA sia<br />
maggiormente orientato agli oggetti rispetto a DCOM, ed offra delle definizioni <strong>di</strong><br />
interfacce più pulite e comprensibili: DCOM, pur essendo più recente <strong>di</strong> CORBA,<br />
nasce come estensione <strong>di</strong> tecnologie preesistenti (COM e DCE) e ne paga il prezzo<br />
(utilizzo <strong>di</strong> ID al posto <strong>di</strong> nomi <strong>per</strong> identificare gli oggetti, interventi su registri <strong>di</strong><br />
sistema, etc.) [Bibl. 20] [Bibl. 21] [Bibl. 22].<br />
2.5.2 Modelli <strong>di</strong> concorrenza: flessibilità, semplicità e sicurezza<br />
L'amministrazione delle risorse <strong>di</strong> sistema da parte <strong>di</strong> un middleware è sempre<br />
soggetta <strong>ad</strong> una serie <strong>di</strong> tr<strong>ad</strong>e-off. I compromessi <strong>ad</strong>ottati nello sviluppo del software<br />
sono fortemente <strong>di</strong>pendenti dall'enfasi che si vuole porre su date caratteristiche, ed i<br />
risultati ottenibili sono spesso molto <strong>di</strong>fferenti: i casi presi in esame si prestano bene<br />
a questo tipo <strong>di</strong> confronto.
<strong>Architetture</strong> <strong>Concorrenti</strong> in prodotti a larga <strong>di</strong>ffusione - Confronti 38<br />
La gestione dei thre<strong>ad</strong> rappresenta una caratteristica molto potente, ma altrettanto<br />
<strong>per</strong>icolosa: un loro corretto management può migliorare notevolmente le prestazioni,<br />
e consentire una più equilibrata <strong>di</strong>stribuzione dei compiti, ma la delicatezza legata ai<br />
meccanismi <strong>di</strong> sincronizzazione, se mal gestita, può causare comportamenti anomali<br />
e danni <strong>ad</strong> un intero sistema.<br />
Un dato molto importante, da prendere in considerazione quando si o<strong>per</strong>a una scelta<br />
sul modello <strong>di</strong> concorrenza, è valutare quanto l'utilizzatore sia interessato <strong>ad</strong><br />
occuparsi <strong>di</strong>rettamente delle prestazioni del suo applicativo (e quin<strong>di</strong> dei thre<strong>ad</strong>), e<br />
quanto, invece, preferisca sia demandato a soluzioni "preconfezionate". Una<br />
maggiore ingerenza nelle questioni riguardanti i thre<strong>ad</strong> comporta, infatti, l'assunzione<br />
da parte del progettista <strong>di</strong> maggiori rischi e la <strong>di</strong>strazione da questioni più inerenti allo<br />
sviluppo delle funzionalità.<br />
L'approccio <strong>di</strong> DCOM, tecnologia che si sviluppa a più basso livello tra quelle<br />
esaminate, con i suoi Apartment, risulta particolarmente ricco <strong>di</strong> soluzioni e capace <strong>di</strong><br />
<strong>ad</strong>attarsi a <strong>di</strong>verse esigenze: il modello è uno dei più completi e consente grande<br />
flessibilità. Il prezzo da pagare è, naturalmente, la competenza necessaria <strong>per</strong> il suo<br />
utilizzo e la scarsa semplicità d'utilizzo; pagato questo scotto, un programmatore<br />
es<strong>per</strong>to del sistema, può ottimizzare i suoi prodotti rispetto alle prestazioni ed<br />
esercitare un controllo maggiore sulle coo<strong>per</strong>azioni tra oggetti.<br />
CORBA, essendo una specifica, si concentra poco sugli aspetti modellistici legati alla<br />
concorrenza, e si limita a fornire solo quei modelli che servono al programmatore <strong>per</strong><br />
garantire che, nel caso <strong>di</strong> co<strong>di</strong>ce "multi-thre<strong>ad</strong>-unaware", non vi siano problemi <strong>di</strong><br />
accesso a sezioni critiche o simili.<br />
ORBacus, d'altro canto, fornendo un'implementazione <strong>di</strong> CORBA, si sofferma<br />
maggiormente sul modello computazionale, e fornisce un set abbastanza ampio <strong>di</strong><br />
modelli collaudati che uniscono controllo e semplicità d'utilizzo. I para<strong>di</strong>gmi sono<br />
in<strong>di</strong>rizzati specificatamente alla ricezione e spe<strong>di</strong>zione <strong>di</strong> messaggi (attività principali<br />
dei componenti <strong>di</strong> un ORB), e consentono una scelta consapevole tra prestazioni<br />
(misurate in tempi <strong>di</strong> attesa <strong>per</strong> la gestione dei messaggi), controllo delle risorse<br />
utilizzate (numero <strong>di</strong> thre<strong>ad</strong> assegnati), e gestione della sicurezza.
<strong>Architetture</strong> <strong>Concorrenti</strong> in prodotti a larga <strong>di</strong>ffusione - Confronti 39<br />
EJB, collocandosi a livello più alto, ha, giustamente, pensato che il programmatore<br />
fosse più interessato alla creazione <strong>di</strong> procedure transazionali, che non al controllo<br />
fisico della macchina, cosa che, essendo le transazioni "oggetti" complicati da<br />
gestire, avrebbe potuto portare a facili errori. Lo scopo principale <strong>di</strong> EJB è garantire<br />
che le delicate transazioni possano avvenire in totale sicurezza, ed il modo migliore<br />
<strong>per</strong> ottenere questo risultato è mantenere il più alto controllo possibile sulle<br />
computazioni interne e, quin<strong>di</strong>, impe<strong>di</strong>re che i programmatori possano interferire con<br />
il modello scelto. Per questa ragione, più che un modello <strong>di</strong> concorrenza, EJB<br />
fornisce dei vincoli che l'utilizzatore <strong>di</strong> applicazioni EJB-compliant deve rispettare:<br />
astenersi assolutamente da manipolare caratteristiche legate all'amministrazione dei<br />
thre<strong>ad</strong>.<br />
Ne risulta che ogni prodotto, secondo l'utilizzo <strong>per</strong> il quale è stato progettato, ha<br />
<strong>ad</strong>ottato modelli <strong>di</strong>fferenti ponendo l'accento su caratteristiche <strong>di</strong>verse che questi<br />
dovevano presentare: DCOM ha puntato sulla flessibilità, CORBA (ed ORBacus)<br />
sulla semplicità, ed EJB sulla sicurezza. Ne segue che, una volta forniti gli strumenti<br />
<strong>per</strong> eseguire modelli concorrenti, molta importanza deve essere data alle interfacce<br />
tra questi e gli utilizzatori.
JADE - Architettura 40<br />
3 JADE<br />
Java Agent DEvelopment Framework è un middleware pensato <strong>per</strong> fornire un<br />
supporto allo sviluppo ed all'esecuzione <strong>di</strong> applicazioni basate sugli Agenti software.<br />
È nato dalla collaborazione tra TILab S.p.A. (precedentemente conosciuto come<br />
CSELT) e gli ambienti universitari, in particolare l'ateneo <strong>di</strong> Parma. Il prodotto è<br />
completamente co<strong>di</strong>ficato in Java e gli applicativi sviluppati usandolo, devono<br />
<strong>ad</strong>o<strong>per</strong>are il medesimo linguaggio <strong>di</strong> programmazione. L'ambiente è compatibile con<br />
gli standard FIPA <strong>per</strong> gli agenti intelligenti, ed è composto da una piattaforma <strong>per</strong><br />
Agenti FIPA-compliant ed un pacchetto Java <strong>per</strong> lo sviluppo degli Agenti [Bibl. 5].<br />
3.1 Architettura<br />
Il software è sud<strong>di</strong>viso in <strong>di</strong>versi pacchetti a seconda delle funzionalità sviluppate. I<br />
più importanti <strong>di</strong> questi sono:<br />
?? j<strong>ad</strong>e.core: implementa il nucleo del sistema. Contiene la classe Agent, che<br />
rappresenta la classe-base <strong>per</strong> tutte le estensioni create dagli utenti, ed un<br />
sotto-pacchetto j<strong>ad</strong>e.core.behaviour, che fornisce gli strumenti <strong>per</strong> assegnare<br />
compiti agli Agenti.<br />
?? j<strong>ad</strong>e.domain: contiene tutte le entità <strong>di</strong> supporto agli Agenti che devono essere<br />
fornite dalla piattaforma secondo le specifiche FIPA, quali l'Agent Management<br />
System (AMS) ed il Directory Facilitator (DF).<br />
?? j<strong>ad</strong>e.proto: raccoglie i protocolli d'interazione standar<strong>di</strong>zzati da FIPA.<br />
I rimanenti pacchetti forniscono i supporti <strong>per</strong> i linguaggi utilizzati nelle comunicazioni<br />
degli agenti (ACL, SL-0 ed i linguaggi definiti dall'utente), <strong>per</strong> il debugging e<br />
l'interfacciamento con gli utilizzatori, strumenti <strong>di</strong> utilità, etc.
JADE - Architettura 41<br />
3.1.1 La piattaforma FIPA-compliant<br />
Diagramma 17 : Architettura <strong>di</strong> una piattaforma FIPA<br />
La piattaforma <strong>per</strong> Agenti implementata in JADE è compatibile con le specifiche FIPA<br />
<strong>per</strong> la separazione e gestione dei servizi che deve fornire agli Agenti-utilizzatori. Una<br />
piattaforma FIPA deve essere composta dai seguenti servizi [Diagramma 17]:<br />
1. L'Agent Management System (AMS), agente che si occupa della su<strong>per</strong>visione e<br />
del controllo degli accessi alla piattaforma, dell'amministrazione dei life-cycle degli<br />
Agenti e della loro registrazione (tramite un servizio <strong>di</strong> "white-page").<br />
2. Il Directory Facilitator (DF), anch'esso un agente, che si occupa della gestione<br />
delle "yellow-page": un servizio che consente agli Agenti <strong>di</strong> registrarsi, <strong>per</strong><br />
rendere <strong>di</strong>sponibile <strong>ad</strong> altri una descrizione dei compiti che possono svolgere e<br />
come rintracciarli.<br />
3. Il Message Transport System (detto anche Agent Communication Channel o<br />
ACC) che si occupa della su<strong>per</strong>visione <strong>di</strong> tutto il traffico <strong>di</strong> messaggi <strong>di</strong> una<br />
piattaforma (sia intra-platform, sia inter-platform).<br />
JADE offre inoltre la possibilità <strong>di</strong> <strong>di</strong>stribuire la piattaforma su "host" <strong>di</strong>fferenti <strong>per</strong><br />
scalarne le mansioni: in questo caso vengono creati <strong>di</strong>fferenti "Agent-Container" (uno
JADE - Architettura 42<br />
<strong>per</strong> macchina) gerarchicamente legati al "main-container" (unico <strong>per</strong> singola<br />
piattaforma) che è quello dove risiedono fisicamente i moduli-Agenti FIPA (AMS, etc.)<br />
[Diagramma 18].<br />
Diagramma 18 : Distribuzione della piattaforma JADE su host <strong>di</strong>fferenti
JADE - Architettura 43<br />
3.1.2 L'amministrazione degli Agenti<br />
Anche alcuni aspetti caratteristici degli agenti si conformano al modello FIPA, in<br />
modo particolare la rappresentazione dello stato interno <strong>di</strong> un istanza e le transizioni<br />
tra questi. Il complesso <strong>di</strong> queste caratteristiche prende il nome <strong>di</strong> "life-cycle"<br />
dell'Agente. FIPA prevede <strong>di</strong>versi stati pensati <strong>per</strong> modellare le attività <strong>di</strong> un Agente:<br />
si parte da quelli "classici", come ACTIVE,SUSPENDED e WAITING, <strong>per</strong> giungere a<br />
stati particolari <strong>per</strong> amministrare la mobilità, quali TRANSIT,COPY e GONE<br />
[Diagramma 19].<br />
Diagramma 19 : Ciclo <strong>di</strong> vita <strong>di</strong> un'Agente FIPA
JADE - Architettura 44<br />
3.1.3 Modello comportamentale<br />
Le attività <strong>di</strong> un Agente, attività che possono essere eseguite solo nello stato<br />
ACTIVE, sono state modellate seguendo il para<strong>di</strong>gma dei behaviour. La classe base,<br />
<strong>per</strong> l'appunto Behaviour, fornisce l'interfaccia <strong>per</strong> amministrare i compiti: contiene<br />
meto<strong>di</strong> specifici <strong>per</strong> le funzioni <strong>di</strong> sospensione/riattivazione <strong>di</strong> un'attività<br />
(block()/restart()), meto<strong>di</strong> <strong>per</strong> l'ispezione dello stato (done(), isRunnable()), e meto<strong>di</strong><br />
astratti che l'utente deve implementare <strong>per</strong> creare il compito che si prefigge<br />
(onStart(), action() ed onEnd()).<br />
Il metodo action() rappresenta il vero nucleo del behaviour, ed è il co<strong>di</strong>ce che esegue<br />
il compito voluto. I meto<strong>di</strong> onStart() ed onEnd() servono nel caso sia necessario<br />
eseguire delle o<strong>per</strong>azioni subito prima o dopo che la action() sia eseguita:<br />
prenotazione e rilascio <strong>di</strong> risorse, o terminazione appropriata <strong>di</strong> un'o<strong>per</strong>azione.
JADE - Architettura 45<br />
Nel caso in cui si abbia a che fare con comportamenti complicati da modellare, la<br />
classe supporta meccanismi <strong>di</strong> composizione che consentono <strong>di</strong> creare behaviour<br />
complessi partendo da gruppi <strong>di</strong> altri più semplici. Il sistema, inoltre, fornisce alcuni<br />
modelli <strong>di</strong> composizione tra i più <strong>ad</strong>o<strong>per</strong>ati (con relative politiche d'esecuzione), già<br />
pronti <strong>per</strong> l'uso: si ha una classe che esegue i suoi sub-behaviour sequenzialmente,<br />
una <strong>per</strong> le esecuzioni concorrenti ed una che implementa un automa a stati finiti<br />
(FSM) [Diagramma 20].<br />
Diagramma 20 : UML Class Diagram <strong>per</strong> la gerarchia dei Behaviour
JADE - Concorrenza: modello Thre<strong>ad</strong>-<strong>per</strong>-Agent 46<br />
Una volta che il programmatore ha esteso, implementandoli, i propri Behaviour,<br />
utilizzando se necessario anche le tecniche <strong>di</strong> composizione, le classi create<br />
possono essere istanziate ed aggiunte all'Agent tramite i meto<strong>di</strong> preposti alla loro<br />
gestione (<strong>ad</strong>dBehaviour()/removeBehaviour()).<br />
3.2 Concorrenza: modello Thre<strong>ad</strong>-<strong>per</strong>-Agent<br />
Ogni Agente JADE è composto da un unico thre<strong>ad</strong> d'esecuzione. Uno scheduler,<br />
implementato dall'Agent e nascosto al programmatore, esegue un algoritmo <strong>di</strong><br />
selezione dei Behaviour <strong>di</strong> tipo round-robin, <strong>di</strong>stribuendo il tempo <strong>di</strong> macchina alle<br />
<strong>di</strong>verse azioni.<br />
In realtà, il tempo <strong>di</strong> macchina non viene <strong>di</strong>stribuito uniformemente sulle <strong>di</strong>verse<br />
istanze dei Behaviour, ma vengono concessi "quanti d'azione" <strong>ad</strong> ogni compito. Il<br />
modello d'esecuzione è infatti non-preemptive, e prevede che <strong>ad</strong> ogni Behaviour sia<br />
concesso <strong>di</strong> eseguire la propria action(), senza interruzioni da parte del sistema:<br />
quando la chiamata <strong>ad</strong> action() ritorna, lo scheduler può selezionare il Behaviour<br />
successivo e ricominciare il ciclo.<br />
La politica <strong>ad</strong>ottata pone delle limitazioni nella creazione dei compiti ai<br />
programmatori. Il corpo <strong>di</strong> una action() non deve contenere cicli infiniti o bloccarsi<br />
indefinitamente: essendo l'Agent single-thre<strong>ad</strong>ed, un Behaviour non può essere<br />
eseguito finché la precedente action() (in esecuzione sul medesimo Agent) non ha<br />
terminato. È Inoltre consigliabile spezzare compiti lunghi in più brevi sotto-compiti da<br />
eseguire sequenzialmente (usando le composizioni): una action() troppo lunga<br />
sottrae tempo <strong>ad</strong> altri compiti, impedendo una <strong>di</strong>stribuzione più equilibrata delle<br />
risorse <strong>di</strong> CPU.<br />
Il modello <strong>di</strong> concorrenza thre<strong>ad</strong>-<strong>per</strong>-Agent <strong>ad</strong>ottato da JADE limita, inoltre, il<br />
parallelismo possibile in tutte quelle situazioni (comuni <strong>per</strong> gli agenti) nelle quali un<br />
compito sia bloccato in attesa <strong>di</strong> eventi comunicativi: un Behaviour che attende su<br />
un'o<strong>per</strong>azione <strong>di</strong> tipo re<strong>ad</strong> bloccante, sospende ogni altra attività dell'agente che<br />
potrebbe nel frattempo essere eseguita. La presenza <strong>di</strong> un ParallelBehaviour è poi<br />
del tutto illusoria: all'interno <strong>di</strong> questo è implementato lo stesso algoritmo round-robin
JADE - Concorrenza: modello Thre<strong>ad</strong>-<strong>per</strong>-Agent 47<br />
che esiste nello scheduler dell'Agent, ed i sub-behaviour sono anch'essi eseguiti<br />
sequenzialmente.<br />
Il su<strong>per</strong>amento <strong>di</strong> questi limiti, passando a modelli multi-thre<strong>ad</strong>ed, può portare, se<br />
ben amministrato, <strong>ad</strong> un aumento dell'efficienza degli Agenti, riducendo in modo<br />
sensibile i tempi d'attesa dovuti ai protocolli <strong>di</strong> comunicazione.
Modello <strong>di</strong> Concorrenza proposto - Granulosità della concorrenza 48<br />
4 Modello <strong>di</strong> Concorrenza proposto<br />
Per fare in modo che un Agent <strong>di</strong>venti realmente multi-thre<strong>ad</strong>ed, occorre<br />
abbandonare l'attuale ciclo <strong>di</strong> scheduling ed esecuzione della relativa action() che sta<br />
alla base del main-loop dell'Agent quando questo è nel suo stato ACTIVE.<br />
Un tipico ciclo <strong>per</strong> amministrare il multi-threa<strong>di</strong>ng dovrebbe comprendere i passi<br />
seguenti:<br />
1. Chiamare lo scheduler <strong>per</strong> ottenere un Behaviour<br />
2. Ottenere un thre<strong>ad</strong> <strong>per</strong> l'esecuzione<br />
3. Fare in modo che il Behaviour sia posto in esecuzione sul thre<strong>ad</strong><br />
4. Occuparsi, solo al suo ritorno, delle o<strong>per</strong>azioni <strong>di</strong> managing dello stato<br />
5. Eseguire i passi precedenti fino a che ci si trova nello stato ACTIVE<br />
Si vuole evidenziare come il passo 4 non comporti necessariamente l'attesa passiva<br />
del ritorno <strong>di</strong> un Behaviour dall'esecuzione: una volta effettuati i passi fino al 3, il ciclo<br />
può occuparsi <strong>di</strong> gestire subito altre richieste d'esecuzione.<br />
4.1 Granulosità della concorrenza<br />
Altra questione da considerare, una volta creata la <strong>di</strong>sponibilità del multi-threa<strong>di</strong>ng, è<br />
la scelta della granulosità che si vuole dare al modello concorrente. Il problema è<br />
simile a quello che si è presentato con l'attuale scelta Thre<strong>ad</strong>-<strong>per</strong>-Agent. Tale<br />
modello può considerarsi concorrente, ma solo a livello della comunità <strong>di</strong> agenti,<br />
nella quale ogni Agent è capace <strong>di</strong> effettuare un'o<strong>per</strong>azione simultaneamente agli<br />
altri; quando si considerano i Behaviour affidati <strong>ad</strong> un singolo Agent, invece, la<br />
concorrenza svanisce.<br />
Richiamando quanto appena esposto, è possibile affidare un unico Thre<strong>ad</strong>-Pool <strong>ad</strong><br />
ogni singolo agente o lasciare che un Agent amministri <strong>di</strong>fferenti pool, affidandoli, in<br />
modo esclusivo, a sottoinsiemi dei suoi Behaviour secondo politiche basate, <strong>ad</strong><br />
esempio, sulla realizzazione <strong>di</strong> servizi <strong>di</strong>fferenti.
Modello <strong>di</strong> Concorrenza proposto - Strumenti <strong>ad</strong>ottati 49<br />
Mentre il primo modello potrebbe essere referenziato come Thre<strong>ad</strong>-Pool-<strong>per</strong>-Agent<br />
(in contrapposizione a quello Thre<strong>ad</strong>-<strong>per</strong>-Agent della versione attuale <strong>di</strong> JADE), si<br />
potrebbe parlare del secondo come del modello ibrido o Thre<strong>ad</strong>-Pool-Cluster. Questo<br />
modello ha il vantaggio, oltre quello <strong>di</strong> una più precisa <strong>di</strong>stribuzione delle risorse,<br />
della possibilità <strong>di</strong> consentire una "transizione morbida" nel passaggio tra modello<br />
single-thre<strong>ad</strong>ed e multi-thre<strong>ad</strong>ed, dando modo all'Agent <strong>di</strong> creare, accanto al modello<br />
concorrente, una partizione con un solo thre<strong>ad</strong> <strong>per</strong> l'esecuzione dei Behaviour più<br />
vecchi.<br />
4.2 Strumenti <strong>ad</strong>ottati<br />
Fondamentale quando si deve implementare un modello è la scelta degli strumenti<br />
da <strong>ad</strong>ottare. In base alla bontà degli strumenti a <strong>di</strong>sposizione, o a quelli che si è<br />
pensato <strong>di</strong> sviluppare, il modello può acquistare caratteristiche importanti quali<br />
flessibilità, robustezza e semplicità d'uso.<br />
4.2.1 Il <strong>di</strong>spatcher<br />
Per implementare il ciclo precedentemente descritto <strong>per</strong> il multi-threa<strong>di</strong>ng, si devono<br />
innanzi tutto separare le azioni <strong>di</strong> scheduling dalla reale esecuzione dei Behaviour,<br />
<strong>per</strong> consentire che le chiamate <strong>ad</strong> action() possano avvenire su thre<strong>ad</strong> <strong>di</strong>fferenti. Per<br />
fare questo è necessario affidarsi <strong>ad</strong> un modulo che, ricevuto un Behaviour, si occupi<br />
<strong>di</strong> porlo in esecuzione in maniera asincrona (senza aspettarne il ritorno), così da<br />
poter processare più richieste mentre i Behaviour sono in esecuzione; tale<br />
componente dovrà, naturalmente, amministrare in modo asincrono anche il ritorno<br />
dalle sessioni d'esecuzione delle action(): questo modulo è quello che si chiama<br />
<strong>di</strong>spatcher.<br />
Il <strong>di</strong>spatcher deve poter attingere, <strong>per</strong> ottenere le risorse <strong>per</strong> l'esecuzione, <strong>ad</strong> una<br />
"fonte <strong>di</strong> thre<strong>ad</strong>", che può o no amministrare lui stesso: tale oggetto è spesso in<strong>di</strong>cato<br />
come Thre<strong>ad</strong>-Pool e può avere a <strong>di</strong>sposizione risorse illimitate o vincolate <strong>ad</strong> un<br />
determinato numero massimo.
Modello <strong>di</strong> Concorrenza proposto - Strumenti <strong>ad</strong>ottati 50<br />
Con la <strong>di</strong>sponibilità <strong>di</strong> un <strong>di</strong>spatcher il ciclo principale <strong>di</strong> un agente <strong>di</strong>venta: se il<br />
<strong>di</strong>spatcher può ottenere un thre<strong>ad</strong>, passagli un Behaviour <strong>per</strong> l'esecuzione, altrimenti<br />
atten<strong>di</strong> che si liberino le risorse già utilizzate.<br />
4.2.2 Lo scheduling ricorsivo<br />
I modelli <strong>di</strong> <strong>di</strong>fferente granulosità della concorrenza sopra descritti possono essere<br />
ulteriormente estesi se combinati con il modello composizionale utilizzato da JADE<br />
<strong>per</strong> la creazione <strong>di</strong> Behaviour complessi. La classe CompositeBehaviour, infatti, può<br />
essere vista come un semplice contenitore <strong>di</strong> Behaviour, ed alcune sue<br />
implementazioni concrete (come ParallelBehaviour) si prestano particolarmente a<br />
tale concetto. Dotando un CompositeBehaviour <strong>di</strong> un <strong>di</strong>spatcher ed un Thre<strong>ad</strong>-Pool<br />
<strong>per</strong>sonali, oltre allo scheduler che implicitamente vi è già contenuto, si può ricostruire<br />
al suo livello la stessa configurazione presente nell'Agent, creando così una struttura<br />
gerarchica: come un Agent amministra i Behaviour affidatigli tramite scheduler e<br />
<strong>di</strong>spatcher, così può fare ogni CompositeBehaviour nei confronti dei suoi<br />
sub-behaviour.<br />
Questa possibilità <strong>per</strong>mette <strong>di</strong> <strong>di</strong>stinguere altri due modelli: il modello<br />
Top-Level-Scheduling, nel quale ogni agente possiede un solo <strong>di</strong>spatcher e<br />
scheduler ai quali è demandato anche il lavoro dei livelli inferiori, ed il modello<br />
Recursive-Scheduling, nel quale ogni CompositeBehaviour rappresenta un'unità<br />
autonoma d'esecuzione.<br />
Mentre il primo modello ha alcuni gravi svantaggi, <strong>ad</strong> esempio il fatto che lo<br />
scheduler mescola tra loro Behaviour top-level e Behaviour provenienti da livelli<br />
inferiori (che potrebbero sottrarre risorse ai primi), il secondo offre un sistema<br />
semplice <strong>per</strong> implementare il modello Thre<strong>ad</strong>-Pool-Cluster: raggruppare tutti i<br />
Behaviour <strong>di</strong> un cluster sotto il controllo <strong>di</strong> un ParallelBehaviour cui è stato affidato il<br />
Thre<strong>ad</strong>-Pool desiderato.
Modello <strong>di</strong> Concorrenza proposto - Mapping <strong>di</strong> modelli con gli strumenti scelti 51<br />
4.3 Mapping <strong>di</strong> modelli con gli strumenti scelti<br />
Gli strumenti scelti <strong>per</strong> implementare la concorrenza, il <strong>di</strong>spatcher e lo scheduling<br />
ricorsivo sfruttando i CompositeBehaviour, consentono una grande flessibilità e la<br />
possibilità <strong>di</strong> <strong>ad</strong>attare il modello a <strong>di</strong>verse esigenze, anche a quelle <strong>di</strong> simulare<br />
modelli già noti.<br />
?? Thre<strong>ad</strong>-<strong>per</strong>-Agent: questo è il modello attualmente <strong>ad</strong>ottato da JADE che deve<br />
necessariamente essere supportato <strong>per</strong> motivi <strong>di</strong> compatibilità. È facilmente<br />
ottenibile fornendo all'Agent un Thre<strong>ad</strong>-Pool single-thre<strong>ad</strong>ed, e facendo lo stesso<br />
con ogni CompositeBehaviour.<br />
?? Thre<strong>ad</strong>-<strong>per</strong>-Behaviour: ogni richiesta d'esecuzione da parte <strong>di</strong> un Behaviour viene<br />
imme<strong>di</strong>atamente assolta da un nuovo thre<strong>ad</strong> (non vi è mai attesa <strong>per</strong> carenza <strong>di</strong><br />
risorse). È ottenibile fornendo il top-<strong>di</strong>spatcher <strong>di</strong> un pool illimitato.<br />
?? Thre<strong>ad</strong>-Pool-<strong>per</strong>-Agent: <strong>ad</strong> ogni Agent è assegnato un numero massimo <strong>di</strong> thre<strong>ad</strong><br />
da sud<strong>di</strong>videre tra le varie richieste. I thre<strong>ad</strong>, una volta creati, potrebbero<br />
rimanere attivi e pronti <strong>per</strong> un nuovo utilizzo più o meno a lungo. È ottenibile<br />
limitando la capacità del pool del <strong>di</strong>spatcher e fissando opportune de<strong>ad</strong>line <strong>per</strong> i<br />
thre<strong>ad</strong>.
Implementazione <strong>di</strong> riferimento - EmbeddedThre<strong>ad</strong> 52<br />
5 Implementazione <strong>di</strong> riferimento<br />
Il presente capitolo descrive le classi create <strong>per</strong> implementare quanto<br />
precedentemente illustrato [cap. 4 Modello <strong>di</strong> Concorrenza proposto]. Per ogni classe<br />
sono descritti: scopi e responsabilità dell'oggetto, i meto<strong>di</strong> d'interfaccia (ed alcuni<br />
privati) sud<strong>di</strong>visi <strong>per</strong> funzionalità correlate, interazioni con sotto-componenti, scelte<br />
realizzative salienti e, quando ritenuto significativo, parti <strong>di</strong> co<strong>di</strong>ce e problemi<br />
incontrati nella realizzazione.<br />
Si prendono successivamente in esame le mo<strong>di</strong>fiche che è necessario apportare a<br />
classi già facenti parte <strong>di</strong> JADE, come Behaviour, CompositeBehaviour e Agent:<br />
questa fase risulta particolarmente delicata <strong>per</strong> motivi <strong>di</strong> compatibilità all'in<strong>di</strong>etro.<br />
Infine s'illustra brevemente un piccola "suite <strong>di</strong> testing" utilizzata <strong>per</strong> verificare la<br />
correttezza delle soluzioni implementate (lungi dal considerarsi completa).<br />
5.1 EmbeddedThre<strong>ad</strong><br />
EmbeddedThre<strong>ad</strong><br />
Mutex<br />
(from EmbeddedThre<strong>ad</strong>)<br />
Diagramma 21 : UML Class Diagram <strong>di</strong> EmbeddedThre<strong>ad</strong> e componenti<br />
1<br />
1
Implementazione <strong>di</strong> riferimento - EmbeddedThre<strong>ad</strong> 53<br />
5.1.1 Scopi e funzionalità<br />
EmbeddedThre<strong>ad</strong><br />
+$ CREATED : int = 1<br />
+$ RUNNING : int = 2<br />
+$ ABOUT_TO_SUSPEND : int = 3<br />
+$ SUSPENDED : int = 4<br />
+$ ABOUT_TO_TERMINATE : int = 5<br />
+$ TERMINATED : int = 6<br />
+$ ABOUT_TO_INTERRUPT : int = 7<br />
+$ INTERRUPTED : int = 8<br />
+ EmbeddedThre<strong>ad</strong>(td : Thre<strong>ad</strong>Dispatcher) : EmbeddedThre<strong>ad</strong><br />
+ setBehaviour(b : Behaviour)<br />
+ getBehaviour() : Behaviour<br />
+ start()<br />
+ run()<br />
+ suspend()<br />
+ suspend(timeout : long) : boolean<br />
+ resume()<br />
+ stop()<br />
+ stop(timeout : long) : boolean<br />
+ interrupt()<br />
+ getThre<strong>ad</strong>State() : int<br />
Diagramma 22 : UML Class Diagram <strong>di</strong> EmbeddedThre<strong>ad</strong><br />
La classe EmbeddedThre<strong>ad</strong> si occupa <strong>di</strong> incapsulare, in maniera appropriata <strong>per</strong> i<br />
particolari scopi cui è prefissa, un thre<strong>ad</strong> <strong>di</strong> Java e <strong>di</strong> fornire all'utente tutti i meto<strong>di</strong><br />
necessari <strong>per</strong> gestirne opportunamente stato e compiti.<br />
Le sue funzionalità possono essere <strong>di</strong>vise in:<br />
1. Ispezione e mo<strong>di</strong>fica dello stato del thre<strong>ad</strong>.<br />
2. Ispezione ed assegnazione dell'attività del thre<strong>ad</strong>.
Implementazione <strong>di</strong> riferimento - EmbeddedThre<strong>ad</strong> 54<br />
5.1.2 Meccanismi <strong>di</strong> sincronizzazione degli accessi: lock<br />
La classe, avendo lo stato interno accessibile da più linee d'esecuzione, <strong>ad</strong>otta<br />
particolari sistemi <strong>di</strong> protezione che, pur non essendo i suoi meto<strong>di</strong> <strong>di</strong>chiarati<br />
synchronized, assicurano una corretta sequenzializzazione delle chiamate alle<br />
funzioni e, <strong>di</strong> conseguenza, il mantenimento della coerenza interna. Particolare<br />
attenzione è stata posta nel limitare i vincoli <strong>di</strong> mutua esclusione alle sole parti <strong>di</strong><br />
co<strong>di</strong>ce in cui fossero strettamente necessari, in modo da consentire la più ampia<br />
sovrapposizione tra le <strong>di</strong>verse chiamate ai meto<strong>di</strong> ed il normale ciclo d'esecuzione<br />
del thre<strong>ad</strong> stesso.<br />
Per ottenere questo risultato si è scelto <strong>di</strong> <strong>ad</strong>ottare, ed incapsulare nella classe, un<br />
oggetto generico (un'istanza <strong>di</strong> Object) con il solo scopo <strong>di</strong> usare il suo monitor<br />
interno (presente in ogni oggetto Java) <strong>per</strong> i fini <strong>di</strong> sincronizzazione: l'oggetto è stato<br />
<strong>ad</strong>o<strong>per</strong>ato come "lock". Il costrutto implementato prevede che, ogni volta che si<br />
debba accedere <strong>ad</strong> una sezione critica <strong>di</strong> co<strong>di</strong>ce, questa sia <strong>di</strong>chiarata all'interno <strong>di</strong><br />
un blocco synchronized sul lock stesso: così facendo, il primo thre<strong>ad</strong> d'esecuzione<br />
che entra nel blocco <strong>di</strong> co<strong>di</strong>ce protetto acquisisce il monitor del lock impedendo <strong>ad</strong><br />
altre chiamate concorrenti <strong>di</strong> accedervi prima del suo rilascio e quin<strong>di</strong>, in generale,<br />
della terminazione del blocco stesso [Riqu<strong>ad</strong>ro 1].<br />
...<br />
private Object stateLock = new Object();<br />
...<br />
public void start()<br />
{<br />
synchronized(stateLock)<br />
{<br />
if(thre<strong>ad</strong>State==CREATED)<br />
{<br />
thre<strong>ad</strong>State=RUNNING;<br />
theThre<strong>ad</strong>.start();<br />
}<br />
}<br />
}<br />
...<br />
Riqu<strong>ad</strong>ro 1 : Sincronizzazione degli accessi allo stato tramite stateLock
Implementazione <strong>di</strong> riferimento - EmbeddedThre<strong>ad</strong> 55<br />
Tale procedura è servita <strong>per</strong> serializzare le mo<strong>di</strong>fiche allo stato del thre<strong>ad</strong><br />
incapsulato in modo che le transizioni del suo automa a stati finiti (FSM) fossero<br />
gestite correttamente.<br />
La scelta dello stateLock si è rivelata utile anche <strong>per</strong> la possibilità, ampiamente<br />
sfruttata, <strong>di</strong> poter usare i suoi meccanismi <strong>di</strong> attesa e notifica (wait() e notify()) <strong>per</strong><br />
sincronizzare gli effettivi cambiamenti <strong>di</strong> stato che avvenivano all'interno del thre<strong>ad</strong><br />
incapsulato.<br />
5.1.3 Ispezione e mo<strong>di</strong>fica dello stato<br />
resume()<br />
Created<br />
Running<br />
start()<br />
suspend()<br />
About to Suspend<br />
interrupt()<br />
stop()<br />
Suspended Terminated<br />
These transitions<br />
are executed<br />
internally by the<br />
thre<strong>ad</strong><br />
Diagramma 23 : UML State Diagram <strong>per</strong> EmbeddedThre<strong>ad</strong><br />
Interrupted<br />
About to Interrupt<br />
interrupt()<br />
About to Terminate
Implementazione <strong>di</strong> riferimento - EmbeddedThre<strong>ad</strong> 56<br />
Molta importanza è stata assegnata nel progetto della classe alla rappresentazione<br />
interna dei <strong>di</strong>versi stati in cui può trovarsi il thre<strong>ad</strong> incapsulato ed <strong>ad</strong> una loro<br />
robustezza <strong>di</strong> gestione.<br />
Il progetto è stato basato sulle critiche (e relativi interventi migliorativi consigliati)<br />
mossi dalla Sun Microsystem stessa ai meto<strong>di</strong> stop(), suspend() e resume() della<br />
classe Thre<strong>ad</strong> <strong>di</strong> Java nel relativo documento delle API [Bibl. 6].<br />
Tale documento pone in enfasi i problemi <strong>di</strong> inconsistenza derivanti dall'uso <strong>di</strong> meto<strong>di</strong><br />
coercitivi <strong>per</strong> terminare o bloccare linee d'esecuzione dall'esterno, visto che tali<br />
chiamate rilasciano ogni monitor del thre<strong>ad</strong> in questione con relativa propagazione<br />
dei danni su altri oggetti che ne erano protetti. Il suggerimento <strong>per</strong> evitare tali<br />
inconvenienti è quello <strong>di</strong> fornire meto<strong>di</strong> che settino delle variabili <strong>per</strong> segnalare<br />
semplicemente i cambiamenti <strong>di</strong> stato e lasciare che sia il thre<strong>ad</strong> stesso a<br />
controllarne i valori durante l'esecuzione ed a prendere gli <strong>ad</strong>eguati provve<strong>di</strong>menti<br />
(sospensione volontaria e riattivazione dell'esecuzione utilizzando wait() e notify() del<br />
monitor interno).<br />
Invece <strong>di</strong> usare più variabili booleane, nel progetto si è <strong>ad</strong>o<strong>per</strong>ata una variabile intera<br />
con significato <strong>di</strong> variabile <strong>di</strong> stato, affiancata da costanti pubbliche <strong>per</strong> facilitarne<br />
l'associazione valore-stato; inoltre <strong>per</strong> la sospensione è stato usato un oggetto<br />
apposito [par. 5.1.6 Mutex].
Implementazione <strong>di</strong> riferimento - EmbeddedThre<strong>ad</strong> 57<br />
Mentre molti stati non hanno bisogno <strong>di</strong> commenti essendo i nomi stessi<br />
autoesplicativi della situazione, una buona parte <strong>di</strong> questi risulta sdoppiata <strong>per</strong><br />
<strong>di</strong>stinguere tra momenti <strong>di</strong>versi che acc<strong>ad</strong>ono durante il life-cycle del thre<strong>ad</strong>: ci si<br />
riferisce alle coppie "STATE" e "ABOUT_TO_STATE" (dove "STATE" sta al posto <strong>di</strong><br />
un effettivo stato del sistema) [Riqu<strong>ad</strong>ro 2].<br />
...<br />
public static final int CREATED=1;<br />
public static final int RUNNING=2;<br />
public static final int ABOUT_TO_SUSPEND=3;<br />
public static final int SUSPENDED=4;<br />
public static final int ABOUT_TO_TERMINATE=5;<br />
public static final int TERMINATED=6;<br />
public static final int ABOUT_TO_INTERRUPT=7;<br />
public static final int INTERRUPTED=8;<br />
private int thre<strong>ad</strong>State;<br />
...<br />
private Object stateLock = new Object();<br />
private Mutex suspendLock=new Mutex();<br />
...<br />
Riqu<strong>ad</strong>ro 2 : EmbeddedThre<strong>ad</strong>: definizione delle costanti <strong>di</strong> stato ed oggetti <strong>per</strong> la sospensione<br />
e la sincronizzazione degli accessi.<br />
L'apparente replica dello stato, è da attribuire al fatto che, mentre la chiamata <strong>ad</strong> un<br />
metodo può solo notificare una "richiesta <strong>di</strong> cambiamento", il cambiamento effettivo<br />
può unicamente avvenite all'interno del thre<strong>ad</strong> d'esecuzione stesso dopo<br />
l'appren<strong>di</strong>mento della avvenuta notifica [Riqu<strong>ad</strong>ro 4]. Questo sistema, consentendo<br />
delle transizioni <strong>di</strong> stato interne al thre<strong>ad</strong> stesso, dà la possibilità <strong>di</strong> esercitare un<br />
controllo più preciso sulla forza con cui un determinato passaggio <strong>di</strong> stato deve<br />
essere compiuto: è così possibile fare al thre<strong>ad</strong> richieste sincrone, asincrone e<br />
temporizzate.<br />
5.1.4 Transizioni temporizzate<br />
Come già detto precedentemente, il sistema <strong>ad</strong>o<strong>per</strong>ato <strong>per</strong> le mo<strong>di</strong>fiche dello stato<br />
<strong>di</strong>stingue tra richieste <strong>di</strong> transizioni ed effettiva esecuzione delle stesse. Una richiesta
Implementazione <strong>di</strong> riferimento - EmbeddedThre<strong>ad</strong> 58<br />
<strong>di</strong> transizione può quin<strong>di</strong> essere gestita <strong>ad</strong> un livello <strong>di</strong> dettaglio maggiore e la<br />
relativa chiamata assumere <strong>di</strong>versi significati:<br />
1. Richiesta asincrona: la richiesta viene inoltrata (mo<strong>di</strong>ficando lo stato in<br />
"ABOUT_TO_STATE") ed il metodo ritorna senza preoccuparsi se il passaggio<br />
sia avvenuto o no [Diagramma 24].<br />
td : Thre<strong>ad</strong><br />
Dispatcher<br />
stop( )<br />
et : Embedded<br />
Thre<strong>ad</strong><br />
action( )<br />
return from action()<br />
b :<br />
Behaviour<br />
Diagramma 24 : UML Sequence Diagram <strong>di</strong> uno stop() asincrono<br />
If action() doesn't<br />
return (is<br />
blocked) stop()<br />
does nothing
Implementazione <strong>di</strong> riferimento - EmbeddedThre<strong>ad</strong> 59<br />
2. Richiesta sincrona: una volta inoltrata la richiesta, il metodo attende sino a che<br />
l'effettiva transizione del thre<strong>ad</strong> ha corso (nel caso questa non dovesse mai<br />
avvenire, il chiamante attende indefinitamente) [Diagramma 25].<br />
td : Thre<strong>ad</strong><br />
Dispatcher<br />
stop(long)<br />
return from stop()<br />
et : Embedded<br />
Thre<strong>ad</strong><br />
action ( )<br />
Diagramma 25 : UML Sequence Diagram <strong>di</strong> uno stop() sincrono<br />
b :<br />
Behaviour<br />
return from action() Stop() waits till<br />
action() returns:<br />
if action() is<br />
blocked also<br />
stop() blocks
Implementazione <strong>di</strong> riferimento - EmbeddedThre<strong>ad</strong> 60<br />
3. Richiesta temporizzata: la richiesta viene inoltrata; se il thre<strong>ad</strong> mo<strong>di</strong>fica il suo<br />
stato entro un parametrizzato lasso <strong>di</strong> tempo, allora la chiamata ritorna<br />
segnalando l'avvenuto successo, altrimenti, una volta trascorso il tempo fissato<br />
senza che sia avvenuta transizione, la chiamata ritorna ugualmente segnalando il<br />
fallimento [Diagramma 26].<br />
td : Thre<strong>ad</strong><br />
Dispatcher<br />
stop(long)<br />
return from stop()<br />
et : Embedded<br />
Thre<strong>ad</strong><br />
action( )<br />
return from action()<br />
b :<br />
Behaviour<br />
Diagramma 26 : UML Sequence Diagram <strong>di</strong> uno stop() con de<strong>ad</strong>line<br />
Stop() waits action()<br />
to return at most for<br />
a specified ammount<br />
of time, then returns<br />
anyway signaling if it<br />
failed or not<br />
Il primo para<strong>di</strong>gma può essere rischioso se il chiamante basa o<strong>per</strong>azioni successive<br />
sulla riuscita della transizione, in quanto questa non fornisce alcuna garanzia che il
Implementazione <strong>di</strong> riferimento - EmbeddedThre<strong>ad</strong> 61<br />
thre<strong>ad</strong> compia effettivamente la mo<strong>di</strong>fica dello stato desiderata; d'altro canto, l'ultima<br />
variante su<strong>per</strong>a il limite della seconda che può causare un blocco in caso <strong>di</strong> problemi<br />
del thre<strong>ad</strong> nell'attuare le richieste.<br />
Tali tipologie <strong>di</strong>fferenti sono state riunite in un unico metodo parametrizzato (<strong>per</strong><br />
stesso genere <strong>di</strong> transizione), il cui argomento è la "de<strong>ad</strong>line" del metodo: il tempo<br />
massimo entro cui il metodo deve tornare al chiamante [Riqu<strong>ad</strong>ro 3].<br />
1 public boolean suspend(long expiration)<br />
2 {<br />
3 De<strong>ad</strong>line.checkExpiration(expiration);<br />
4 synchronized(stateLock)<br />
5 {<br />
6 if(thre<strong>ad</strong>State==RUNNING || thre<strong>ad</strong>State==ABOUT_TO_SUSPEND)<br />
7 {<br />
8 thre<strong>ad</strong>State=ABOUT_TO_SUSPEND;<br />
9 try<br />
10 {<br />
11 if(expiration!=De<strong>ad</strong>line.NOW)<br />
12 if(expiration==De<strong>ad</strong>line.NEVER) stateLock.wait();<br />
13 else stateLock.wait(expiration);<br />
14 }<br />
15 catch(InterruptedException ie) {}<br />
16 }<br />
17 return (thre<strong>ad</strong>State==SUSPENDED);<br />
18 }<br />
19 }<br />
Riqu<strong>ad</strong>ro 3 : EmbeddedThre<strong>ad</strong>.suspend(expiration)<br />
Per standar<strong>di</strong>zzare l'argomento <strong>di</strong> tali generi <strong>di</strong> meto<strong>di</strong> "temporizzati", è stata creata<br />
una classe apposita, evitando così <strong>di</strong> dover duplicare costanti e creare confusione tra<br />
i valori <strong>di</strong> queste [par. 5.5.2 De<strong>ad</strong>line].<br />
SI è scelto <strong>di</strong> applicare questo costrutto a transizioni considerate critiche <strong>per</strong> la loro<br />
importanza e <strong>per</strong> il controllo che si voleva avere sul loro effettivo acca<strong>di</strong>mento:<br />
suspend() e stop(); anche interrupt() è stata, in un certo senso, temporizzata, ma in<br />
maniera <strong>di</strong>fferente e nascosta all'utente, che quando interrompe un thre<strong>ad</strong> si aspetta<br />
che questo acc<strong>ad</strong>a in ogni caso: si vedrà che ciò non sempre è vero [par. 5.4.3<br />
Meto<strong>di</strong> non interrompibili].
Implementazione <strong>di</strong> riferimento - EmbeddedThre<strong>ad</strong> 62<br />
Nel Riqu<strong>ad</strong>ro 3 si possono notare, nelle linee 11-13, i meccanismi <strong>di</strong> temporizzazione<br />
<strong>di</strong>fferenziati secondo il parametro ed alla linea 17 la notifica del successo o meno<br />
della transizione; nel Riqu<strong>ad</strong>ro 4 invece appaiono le linee <strong>di</strong> co<strong>di</strong>ce in cui il thre<strong>ad</strong>,<br />
una volta controllata l'avvenuta richiesta <strong>di</strong> cambiamento, la esegue e ne da notifica<br />
usando il lock: alle linee 13-17 abbiamo la sospensione, alle 21-25 la terminazione<br />
ed alle linee 29-33 l'avvenuta interruzione.<br />
1 public void run()<br />
2 {<br />
3 try<br />
4 {<br />
5 while(thre<strong>ad</strong>State!=ABOUT_TO_TERMINATE)<br />
6 {<br />
7 if(theBehaviour!=null) theBehaviour.action();<br />
8 suspend();<br />
9 theThre<strong>ad</strong>Dispatcher.requestBehaviour(this);<br />
10<br />
11 synchronized(stateLock)<br />
12 {<br />
13 if(thre<strong>ad</strong>State==ABOUT_TO_SUSPEND)<br />
14 {<br />
15 thre<strong>ad</strong>State=SUSPENDED;<br />
16 stateLock.notify();<br />
17 }<br />
18 }<br />
19 if(thre<strong>ad</strong>State==SUSPENDED) suspendLock.mWait();<br />
20 }<br />
21 synchronized(stateLock)<br />
22 {<br />
23 thre<strong>ad</strong>State=TERMINATED;<br />
24 stateLock.notify();<br />
25 }<br />
26 }<br />
27 catch(InterruptedException ie)<br />
28 {<br />
29 synchronized(stateLock)<br />
30 {<br />
31 thre<strong>ad</strong>State=INTERRUPTED;<br />
32 stateLock.notify();<br />
33 }<br />
34 theThre<strong>ad</strong>Dispatcher.notifyInterrupted(this);<br />
35 }<br />
36 }<br />
Riqu<strong>ad</strong>ro 4 : EmbeddedThre<strong>ad</strong>.run()
Implementazione <strong>di</strong> riferimento - EmbeddedThre<strong>ad</strong> 63<br />
5.1.5 Ispezione ed assegnazione dell'attività<br />
L'attività <strong>di</strong> un EmbeddedThre<strong>ad</strong> consiste nell'esecuzione del metodo action() del<br />
Behaviour assegnatogli.<br />
Il modello non-preemptive <strong>di</strong> JADE prevede che il tempo d'esecuzione sia <strong>di</strong>viso in<br />
quanti d'azione, ed il metodo action() ne racchiude appunto uno. Il ciclo principale <strong>di</strong><br />
un EmbeddedThre<strong>ad</strong> prevede l'esecuzione della action() e la volontaria sospensione<br />
fino all'eventuale assegnazione <strong>di</strong> un nuovo Behaviour da eseguire: ciò fa sì che<br />
momenti ideali <strong>per</strong> questo compito siano gli stati CREATED e SUSPENDED, mentre<br />
<strong>di</strong>viene privo <strong>di</strong> significato eseguire un cambiamento <strong>di</strong> compito durante stati come<br />
RUNNING o TERMINATED.
Implementazione <strong>di</strong> riferimento - EmbeddedThre<strong>ad</strong> 64<br />
Il metodo setBehaviour() si occupa dell'assegnazione del Behaviour e dei relativi<br />
controlli sullo stato <strong>per</strong> evitare errori; analogamente, un metodo getBehaviour()<br />
restituisce il "comportamento" attualmente in esecuzione sul thre<strong>ad</strong>, <strong>per</strong> i compiti <strong>di</strong><br />
notifica e rilevamento <strong>di</strong> sta<strong>di</strong> su<strong>per</strong>iori [par. 5.2 Thre<strong>ad</strong>Dispatcher] [Diagramma 27].<br />
Diagramma 27 : UML Sequence Diagram <strong>per</strong> l'assegnazione, ed ispezione, dei Behaviour <strong>ad</strong> un<br />
EmbeddedThre<strong>ad</strong><br />
s :<br />
Scheduler<br />
b:=schedule ( )<br />
notifyExecuted (b)<br />
td : Thre<strong>ad</strong><br />
Dispatcher<br />
assignBehaviour (et,s)<br />
setBehaviour (b)<br />
requestBehaviour (et)<br />
b:=getBehaviour ( )<br />
s:=get b's scheduler<br />
et : Embedded<br />
Thre<strong>ad</strong>
Implementazione <strong>di</strong> riferimento - EmbeddedThre<strong>ad</strong> 65<br />
5.1.6 Mutex<br />
La classe Mutex, incapsulata all'interno <strong>di</strong> EmbeddedThre<strong>ad</strong>, implementa il costrutto<br />
del "semaforo", utilizzato dalla classe contenitrice <strong>per</strong> l'effettiva sospensione del<br />
thre<strong>ad</strong> d'esecuzione.<br />
L'utilizzo del semaforo, al posto del più semplice oggetto generico utilizzato come<br />
"lock", si è reso necessario <strong>per</strong> evitare problemi <strong>di</strong> corse critiche che avrebbero<br />
potuto portare al de<strong>ad</strong>lock.<br />
I normali meto<strong>di</strong> wait() e notify() forniti dalla classe Object, infatti, funzionano<br />
correttamente quando si è certi che ogni notify() segua temporalmente il relativo<br />
wait(), in modo da risvegliare un thre<strong>ad</strong> così sospeso, ma nel caso in cui,<br />
malauguratamente, il wait() tar<strong>di</strong> ed il notify() venga lanciato prima del dovuto, tale<br />
messaggio <strong>di</strong> notifica va <strong>per</strong>so senza lasciare traccia, causando l'attesa illimitata da<br />
parte del successivo wait().<br />
Mutex<br />
(from EmbeddedThre<strong>ad</strong>)<br />
+ Mutex(initVal : int = 0) : Mutex<br />
+ mSignal() : int<br />
+ mWait() : int<br />
Diagramma 28 : UML Class Diagram <strong>di</strong> EmbeddedThre<strong>ad</strong>::Mutex<br />
Per evitare questo inconveniente, la classe Mutex definisce i meto<strong>di</strong> mWait() ed<br />
mSignal(), che a <strong>di</strong>fferenza degli analoghi suddetti, lasciano traccia esplicita della<br />
loro esecuzione in una variabile contatore interna alla classe stessa. Detta variabile<br />
intera, solitamente inizializzata a zero, viene decrementata <strong>ad</strong> ogni esecuzione <strong>di</strong><br />
mWait() e, viceversa, incrementata <strong>ad</strong> ogni mSignal(). Internamente a tali meto<strong>di</strong><br />
sono poi eseguite le vere chiamate a wait() e notify(), ma con<strong>di</strong>zionatamente al<br />
valore della variabile (così da formare una sorta <strong>di</strong> "guar<strong>di</strong>a" alle sospensioni<br />
indesiderate): un wait() viene eseguito solo se il contatore è minore o uguale a zero.
Implementazione <strong>di</strong> riferimento - EmbeddedThre<strong>ad</strong> 66<br />
Con questo sistema un mSignal() che dovesse arrivare prima del relativo mWait(),<br />
porterebbe da zero <strong>ad</strong> uno il valore della variabile interna, che <strong>di</strong>venendo maggiore<br />
<strong>di</strong> zero eviterebbe il blocco successivo.<br />
Controllando il co<strong>di</strong>ce relativo all'uso <strong>di</strong> Mutex all'interno del metodo run() <strong>di</strong><br />
EmbeddedThre<strong>ad</strong> [Riqu<strong>ad</strong>ro 5] ci si può rendere conto delle motivazioni che hanno<br />
portato a questa scelta:<br />
...<br />
synchronized(stateLock)<br />
{<br />
if(thre<strong>ad</strong>State==ABOUT_TO_SUSPEND)<br />
{<br />
thre<strong>ad</strong>State=SUSPENDED;<br />
stateLock.notify();<br />
}<br />
}<br />
if(thre<strong>ad</strong>State==SUSPENDED) suspendLock.mWait();<br />
...<br />
Riqu<strong>ad</strong>ro 5 : EmbeddedThre<strong>ad</strong>.run(): blocco <strong>per</strong> la sospensione del thre<strong>ad</strong>.<br />
StateLock, come già detto, è l'oggetto usato <strong>per</strong> implementare la mutua esclusione<br />
tra i blocchi che devono ispezionare e mo<strong>di</strong>ficare lo stato interno del thre<strong>ad</strong>, mentre<br />
suspendLock è l'istanza <strong>di</strong> Mutex <strong>di</strong> nostro interesse.
Implementazione <strong>di</strong> riferimento - EmbeddedThre<strong>ad</strong> 67<br />
Bisogna innanzi tutto notare che la sospensione sul semaforo avviene al <strong>di</strong> fuori del<br />
blocco synchronized in quanto una wait() annidata su più oggetti sincronizzati libera<br />
soltanto il monitor dell'oggetto più interno, lasciando bloccati i livelli su<strong>per</strong>iori: il<br />
blocco relativo alla mo<strong>di</strong>fica dello stato del thre<strong>ad</strong> non sarebbe mai abbandonato,<br />
poiché i notify() necessari al risveglio del mutex vengono lanciati anche loro<br />
dall'interno <strong>di</strong> blocchi sincronizzati sul monitor del medesimo oggetto (stateLock)<br />
[Riqu<strong>ad</strong>ro 6].<br />
public void resume()<br />
{<br />
synchronized(stateLock)<br />
{<br />
if(thre<strong>ad</strong>State==ABOUT_TO_SUSPEND || thre<strong>ad</strong>State==SUSPENDED)<br />
{<br />
if(thre<strong>ad</strong>State==SUSPENDED)<br />
{<br />
thre<strong>ad</strong>State=RUNNING;<br />
suspendLock.mSignal();<br />
}<br />
else thre<strong>ad</strong>State=RUNNING;<br />
}<br />
}<br />
}<br />
Riqu<strong>ad</strong>ro 6 : EmbeddedThre<strong>ad</strong>.resume()<br />
Nel Riqu<strong>ad</strong>ro 5 si può osservare come, una volta liberato il monitor dello stateLock e<br />
prima <strong>di</strong> eseguire l'effettiva sospensione sul mutex, la classe contenitrice<br />
EmbeddedThre<strong>ad</strong> <strong>di</strong>chiari già <strong>di</strong> essere "SUSPENDED": questo è il punto in cui<br />
potrebbe verificarsi la con<strong>di</strong>zione <strong>di</strong> corsa critica; un resume(), <strong>ad</strong> esempio,<br />
riporterebbe lo stato a RUNNING ed eseguirebbe un notify() che andrebbe <strong>per</strong>duto; il<br />
successivo wait() bloccherebbe il thre<strong>ad</strong> in un fittizio stato "attivo" indefinitamente.
Implementazione <strong>di</strong> riferimento - Thre<strong>ad</strong>Dispatcher 68<br />
5.2 Thre<strong>ad</strong>Dispatcher<br />
Re<strong>ad</strong>yThre<strong>ad</strong>sManager<br />
(from Thre<strong>ad</strong>Dispatcher)<br />
Thre<strong>ad</strong>Dispatcher<br />
Diagramma 29 : UML Class Diagram <strong>di</strong> Thre<strong>ad</strong>Dispatcher e componenti<br />
1<br />
1<br />
1<br />
1<br />
RequestsManager<br />
(from Thre<strong>ad</strong>Dispatcher)
Implementazione <strong>di</strong> riferimento - Thre<strong>ad</strong>Dispatcher 69<br />
5.2.1 Scopi e funzionalità<br />
+$ UNLIMITED_POOL : int = 0<br />
+$ SINGLE_THREADED : int = 1<br />
Diagramma 30 : UML Class Diagram <strong>di</strong> Thre<strong>ad</strong>Dispatcher<br />
La classe Thre<strong>ad</strong>Dispatcher è l'oggetto che si occupa fisicamente <strong>di</strong> porre in<br />
esecuzione i Behaviour su un thre<strong>ad</strong>.<br />
Amministra un pool <strong>di</strong> EmbeddedThre<strong>ad</strong>, che può variare da zero <strong>ad</strong> infinito<br />
(potenzialmente), e ne gestisce il life-cycle. Comunica da un lato con le istanze <strong>di</strong><br />
EmbeddedThre<strong>ad</strong> <strong>di</strong> cui si occupa, dall'altro con uno o più scheduler, ai quali<br />
richiede, secondo necessità, i Behaviour.<br />
Le funzionalità offerte possono sud<strong>di</strong>vidersi in:<br />
1. Ispezione e mo<strong>di</strong>fica dei parametri del Thre<strong>ad</strong>-Pool<br />
2. Dispatching e gestione dei thre<strong>ad</strong><br />
Thre<strong>ad</strong>Dispatcher<br />
+ Thre<strong>ad</strong>Dispatcher(a : Agent, s : Scheduler, td : TimerDispatcher) : Thre<strong>ad</strong>Dispatcher<br />
+ Thre<strong>ad</strong>Dispatcher(cb : CompositeBehaviour) : Thre<strong>ad</strong>Dispatcher<br />
+ setRe<strong>ad</strong>yThre<strong>ad</strong>sExpiration(expiration : long)<br />
+ getRe<strong>ad</strong>yThre<strong>ad</strong>sExpiration() : long<br />
+ setPoolCapacity(thre<strong>ad</strong>sNumber : int)<br />
+ getPoolCapacity() : int<br />
+ <strong>di</strong>spatch()<br />
+ stop(expiration : long)<br />
+ stopRunningThre<strong>ad</strong>s(expiration : long) : int<br />
+ requestScheduling(s : Scheduler) : boolean<br />
+ requestBehaviour(et : EmbeddedThre<strong>ad</strong>)<br />
+ notifyInterrupted(et : EmbeddedThre<strong>ad</strong>)<br />
- assignBehaviour(et : EmbeddedThre<strong>ad</strong>, s : Scheduler)<br />
3. Amministrazione delle richieste degli scheduler e dei thre<strong>ad</strong>
Implementazione <strong>di</strong> riferimento - Thre<strong>ad</strong>Dispatcher 70<br />
5.2.2 Ispezione e mo<strong>di</strong>fica dei parametri del Thre<strong>ad</strong>-Pool<br />
La classe offre la possibilità <strong>di</strong> agire sui parametri che caratterizzano il pool <strong>di</strong> thre<strong>ad</strong><br />
anche a tempo d'esecuzione. Le caratteristiche mo<strong>di</strong>ficabili del pool sono il numero<br />
massimo <strong>di</strong> thre<strong>ad</strong> consentite (la capacità del pool) ed il <strong>per</strong>iodo massimo <strong>di</strong> inattività<br />
concesso <strong>ad</strong> un thre<strong>ad</strong> prima <strong>di</strong> venire eliminato <strong>per</strong> liberare risorse <strong>di</strong> sistema.<br />
Il <strong>per</strong>iodo d'inattività dei thre<strong>ad</strong> è amministrato tramite i meto<strong>di</strong><br />
setRe<strong>ad</strong>yThre<strong>ad</strong>sExpiration() e getRe<strong>ad</strong>yThre<strong>ad</strong>sExpiration() che richiamano, <strong>per</strong><br />
l'effettiva esecuzione i relativi meto<strong>di</strong> della classe Re<strong>ad</strong>yThre<strong>ad</strong>sManager<br />
(incapsulata all'interno <strong>di</strong> Thre<strong>ad</strong>Dispatcher) [par. 5.2.5 Re<strong>ad</strong>yThre<strong>ad</strong>sManager].<br />
La capacità del pool viene invece gestita <strong>di</strong>rettamente dalla classe e dai suoi meto<strong>di</strong><br />
setPoolCapacity() e getPoolCapacity().Per agevolare la gestione della capacità sono<br />
state introdotte nella classe alcune variabili statiche <strong>per</strong> associare valori numerici a<br />
stati, come SINGLE_THREADED <strong>per</strong> assegnare un solo thre<strong>ad</strong> al pool e<br />
UNLIMIED_POOL <strong>per</strong> consentire modelli senza limite massimo <strong>per</strong> il numero <strong>di</strong><br />
thre<strong>ad</strong> (Thre<strong>ad</strong>-<strong>per</strong>-Behaviour).<br />
Il metodo setPoolCapacity() non si limita a mo<strong>di</strong>ficare il valore della variabile<br />
associata al limite massimo del pool, ma effettua anche o<strong>per</strong>azioni <strong>di</strong> notifica e<br />
rinormalizzazione delle risorse allocate. Quando la capacità massima del pool viene<br />
aumentata, potrebbe infatti acc<strong>ad</strong>ere che un Thre<strong>ad</strong>Dispatcher, precedentemente<br />
bloccato in attesa che si liberassero dei thre<strong>ad</strong>, abbia ora la possibilità <strong>di</strong> mandare in<br />
esecuzione dei nuovi Behaviour. Analogamente, quando il pool viene ridotto è<br />
necessario fare in modo che il numero <strong>di</strong> istanze <strong>di</strong> EmbeddedThre<strong>ad</strong> non su<strong>per</strong>i, nel<br />
computo complessivo <strong>di</strong> re<strong>ad</strong>y e running, la nuova capacità. Nel caso questo<br />
avvenga si procederà con l'eliminare innanzitutto i re<strong>ad</strong>yThre<strong>ad</strong> in eccesso, e, se ciò<br />
non dovesse bastare, si procederà con l'eliminazione degli attuali running solo dopo<br />
che la loro sessione d'esecuzione ha termine.<br />
5.2.3 Dispatching e gestione dei thre<strong>ad</strong><br />
L'o<strong>per</strong>azione principale <strong>di</strong> un Thre<strong>ad</strong>Dispatcher, fare in modo che ogni Behaviour<br />
venga eseguito da un thre<strong>ad</strong>, è effettuata tramite chiamate a <strong>di</strong>spatch(). Tale
Implementazione <strong>di</strong> riferimento - Thre<strong>ad</strong>Dispatcher 71<br />
metodo, appoggiandosi alle classi incapsulate, attende fintanto che non vi siano uno<br />
scheduler che abbia un Behaviour da eseguire e un EmbeddedThre<strong>ad</strong> <strong>di</strong>sponibile<br />
<strong>per</strong> l'esecuzione. Il thre<strong>ad</strong> <strong>per</strong> l'esecuzione può essere creato, recu<strong>per</strong>ato dai re<strong>ad</strong>y,<br />
o appena tornato da una sessione precedente, secondo che la capacità del pool sia<br />
stata raggiunta o no.<br />
Il metodo ha due modalità d'esecuzione <strong>di</strong>stinte. Nel caso il pool sia <strong>di</strong> tipo<br />
single-thre<strong>ad</strong>ed, nessuna istanza <strong>di</strong> EmbeddedThre<strong>ad</strong> viene creata, ma il corpo<br />
stesso <strong>di</strong> <strong>di</strong>spatch() contiene il co<strong>di</strong>ce <strong>per</strong> un'esecuzione <strong>di</strong>retta <strong>di</strong> action() entro lo<br />
stesso thre<strong>ad</strong> dell'invocante (che attende quin<strong>di</strong> la sua esecuzione). Nelle situazioni<br />
multi-thre<strong>ad</strong>ed, invece, <strong>di</strong>spatch() assegna il Behaviour da eseguire <strong>ad</strong> un<br />
EmbeddedThre<strong>ad</strong> e ritorna imme<strong>di</strong>atamente.
Implementazione <strong>di</strong> riferimento - Thre<strong>ad</strong>Dispatcher 72<br />
Il metodo <strong>di</strong>spatch() può lanciare l'eccezione InterruptedException nel caso fosse<br />
interrotto mentre in attesa delle con<strong>di</strong>zioni <strong>per</strong> il <strong>di</strong>spatching. Se l'interruzione<br />
avviene, invece, durante l'esecuzione <strong>di</strong> action() (solo <strong>per</strong> il caso single-thre<strong>ad</strong>ed),<br />
questa viene catturata internamente e gestita tramite la catena <strong>di</strong> segnalazioni che<br />
corre tra i componenti del modello [Riqu<strong>ad</strong>ro 7].<br />
public synchronized void <strong>di</strong>spatch() throws InterruptedException<br />
{<br />
if(state==ACTIVE)<br />
{<br />
while(!requests.thereIsARe<strong>ad</strong>yThre<strong>ad</strong>() &&<br />
(!requests.thereIsARe<strong>ad</strong>yScheduler() || poolIsFull()))<br />
{<br />
if(theAgent!=null && hasNoRunningThre<strong>ad</strong>sBelow())<br />
theAgent.doIdle();<br />
wait();<br />
}<br />
Scheduler theScheduler=requests.getRe<strong>ad</strong>yScheduler();<br />
if(poolCapacity==SINGLE_THREADED)<br />
{<br />
Behaviour theBehaviour=null;<br />
try<br />
{<br />
theBehaviour=theScheduler.schedule();<br />
actionWrap<strong>per</strong>(theBehaviour);<br />
theScheduler.notifyExecuted(theBehaviour);<br />
}<br />
catch(InterruptedException ie)<br />
{<br />
theScheduler.notifyInterrupted(theBehaviour);<br />
}<br />
}<br />
else<br />
synchronized(re<strong>ad</strong>yThre<strong>ad</strong>s)<br />
{<br />
EmbeddedThre<strong>ad</strong> theThre<strong>ad</strong>;<br />
if(requests.thereIsARe<strong>ad</strong>yThre<strong>ad</strong>())<br />
theThre<strong>ad</strong>=requests.getRe<strong>ad</strong>yThre<strong>ad</strong>();<br />
else if(re<strong>ad</strong>yThre<strong>ad</strong>s.size()>0)<br />
theThre<strong>ad</strong>=re<strong>ad</strong>yThre<strong>ad</strong>s.get();<br />
else<br />
theThre<strong>ad</strong>=new EmbeddedThre<strong>ad</strong>(this);<br />
assignBehaviour(theThre<strong>ad</strong>,theScheduler);<br />
}<br />
}<br />
}<br />
Riqu<strong>ad</strong>ro 7 : Thre<strong>ad</strong>Dispatcher.<strong>di</strong>spatch()
Implementazione <strong>di</strong> riferimento - Thre<strong>ad</strong>Dispatcher 73<br />
Il co<strong>di</strong>ce <strong>di</strong> <strong>di</strong>spatch() appare particolarmente compatto, non, come si potrebbe<br />
pensare, <strong>per</strong> la semplicità dell'o<strong>per</strong>azione svolta, ma poiché buona parte della<br />
complessità è nascosta, ed incapsulata, nei <strong>di</strong>versi meto<strong>di</strong> interni che richiama.<br />
Oltre alle o<strong>per</strong>azioni <strong>di</strong> <strong>di</strong>spatching, la classe offre due meto<strong>di</strong> <strong>per</strong> la gestione<br />
dell'interruzione delle attività del Thre<strong>ad</strong>Dispatcher utili <strong>per</strong> o<strong>per</strong>azioni riguardanti la<br />
mobilità, o nel caso richiesto da altre attività (come il metodo Scheduler.reset()<br />
[par. 5.3.7 Cambiamenti <strong>di</strong> stato imposti ai Behaviour dall'esterno]): questi sono<br />
stop() e stopRunningThre<strong>ad</strong>s() entrambi con parametro una de<strong>ad</strong>line [par. 5.4<br />
Gestione delle interruzioni]. Mentre il secondo interrompe i running thre<strong>ad</strong> che non<br />
terminano entro il limite fissato dalla de<strong>ad</strong>line (e pone quelli che finiscono tra i re<strong>ad</strong>y),<br />
il primo blocca ogni tipo d'attività, interrompe i running, elimina i re<strong>ad</strong>y e rimuove le<br />
richieste degli scheduler [par. 5.2.6 RequestsManager].<br />
5.2.4 Amministrazione delle richieste degli scheduler e dei thre<strong>ad</strong><br />
Per evitare il polling dei thre<strong>ad</strong> del pool e degli scheduler che si affidano <strong>ad</strong> esso, il<br />
Thre<strong>ad</strong>Dispatcher ricorre a meccanismi <strong>di</strong> notifica da parte degli EmbeddedThre<strong>ad</strong><br />
che hanno terminato la sessione d'esecuzione dei Behaviour loro assegnati e degli<br />
scheduler che hanno <strong>di</strong>sponibilità <strong>di</strong> nuovi Behaviour da schedulare [par. 5.3.5.2<br />
Richieste <strong>di</strong> scheduling].
Implementazione <strong>di</strong> riferimento - Thre<strong>ad</strong>Dispatcher 74<br />
La gestione <strong>di</strong> tali richieste è affidata interamente al RequestsManager [par. 5.2.6<br />
RequestsManager], a parte le o<strong>per</strong>azioni <strong>di</strong> notifica conseguenti la terminazione<br />
dell'esecuzione <strong>di</strong> un Behaviour [Riqu<strong>ad</strong>ro 8] [Diagramma 31].<br />
public synchronized void requestBehaviour(EmbeddedThre<strong>ad</strong> et)<br />
{<br />
Behaviour b=et.getBehaviour();<br />
if(b!=null)<br />
{<br />
Scheduler s=(Scheduler) associationBS.remove(b);<br />
s.notifyExecuted(b);<br />
}<br />
if(state==ACTIVE)<br />
{<br />
requests.<strong>ad</strong>d(et);<br />
notify();<br />
}<br />
else<br />
{<br />
runningThre<strong>ad</strong>s.remove(et);<br />
et.stop(De<strong>ad</strong>line.NEVER);<br />
}<br />
}<br />
Riqu<strong>ad</strong>ro 8 : Thre<strong>ad</strong>Dispatcher.requestBehaviour()
Implementazione <strong>di</strong> riferimento - Thre<strong>ad</strong>Dispatcher 75<br />
td : Thre<strong>ad</strong><br />
Dispatcher<br />
EmbeddedThre<strong>ad</strong> (td)<br />
setBehaviour (b1)<br />
start ( )<br />
requestBehaviour (et)<br />
et : Embedded<br />
Thre<strong>ad</strong><br />
action( )<br />
suspend ( )<br />
At this point the thre<strong>ad</strong> is<br />
waiting on the Mutex<br />
setBehaviour (b2)<br />
resume ( )<br />
requestBehaviour (et)<br />
stop ( )<br />
suspend ( )<br />
b1 :<br />
Behaviour<br />
action( )<br />
b2 :<br />
Behaviour<br />
Diagramma 31 : UML Sequence Diagram <strong>per</strong> il ciclo base esecuzione/richiesta <strong>di</strong> Behaviour
Implementazione <strong>di</strong> riferimento - Thre<strong>ad</strong>Dispatcher 76<br />
5.2.5 Re<strong>ad</strong>yThre<strong>ad</strong>sManager<br />
La classe Re<strong>ad</strong>yThre<strong>ad</strong>sManager, incapsulata in Thre<strong>ad</strong>Dispatcher, è stata pensata<br />
<strong>per</strong> agevolare le o<strong>per</strong>azioni riguardanti la gestione <strong>di</strong> quei thre<strong>ad</strong> che, avendo già<br />
eseguito il loro compito ed essendo il Thre<strong>ad</strong>Dispatcher privo <strong>di</strong> nuovi Behaviour da<br />
eseguire, non hanno momentaneamente nulla da fare e, teoricamente, potrebbero<br />
essere eliminati: quando il Thre<strong>ad</strong>Dispatcher avrà nuovi compiti da mandare in<br />
esecuzione, allora saranno creati dei nuovi thre<strong>ad</strong> appositamente <strong>per</strong> lo scopo.<br />
Le o<strong>per</strong>azioni <strong>di</strong> creazione-<strong>di</strong>struzione <strong>di</strong> thre<strong>ad</strong>, anche se meno impegnative delle<br />
corrispettive <strong>per</strong> i task, sono comunque <strong>di</strong>spen<strong>di</strong>ose in termini <strong>di</strong> risorse <strong>di</strong> macchina<br />
(con particolare riferimento al tempo) ed in caso <strong>di</strong> frequenti richieste <strong>di</strong> questo tipo,<br />
come certamente sarebbe nel caso in esame, data la piccola mole <strong>di</strong> lavoro affidata<br />
ai thre<strong>ad</strong> (si tratta <strong>di</strong> eseguire il solo co<strong>di</strong>ce della action()), andrebbe sprecato molto<br />
tempo <strong>di</strong> calcolo.<br />
(from Thre<strong>ad</strong>Dispatcher)<br />
Re<strong>ad</strong>yThre<strong>ad</strong>sManager<br />
+ Re<strong>ad</strong>yThre<strong>ad</strong>sManager(td : TimerDispatcher = null) : Re<strong>ad</strong>yThre<strong>ad</strong>sManager<br />
+ assignTimerDispatcher(td : TimerDispatcher)<br />
+ setExpiration(millis : long)<br />
+ getExpiration() : long<br />
+ <strong>ad</strong>d(et : EmbeddedThre<strong>ad</strong>) : boolean<br />
+ get() : EmbeddedThre<strong>ad</strong><br />
+ remove(et : EmbeddedThre<strong>ad</strong>) : boolean<br />
+ stopOne()<br />
+ stopAll()<br />
+ doTimeout(t : Timer)<br />
+ contains(et : EmbeddedThre<strong>ad</strong>) : boolean<br />
+ size() : int<br />
Diagramma 32 : UML Class Diagram <strong>di</strong> Thre<strong>ad</strong>Dispatcher::Re<strong>ad</strong>yThre<strong>ad</strong>sManager<br />
Per limitare il numero d'o<strong>per</strong>azioni <strong>di</strong> creazione-<strong>di</strong>struzione <strong>di</strong> thre<strong>ad</strong>, si è pensato <strong>di</strong><br />
introdurre una specie <strong>di</strong> "deposito" <strong>per</strong> i thre<strong>ad</strong> privi <strong>di</strong> compiti, in modo che possano
Implementazione <strong>di</strong> riferimento - Thre<strong>ad</strong>Dispatcher 77<br />
essere imme<strong>di</strong>atamente <strong>di</strong>sponibili quando si presenti l'evenienza: un Behaviour che<br />
debba essere eseguito, prima <strong>di</strong> portare alla creazione <strong>di</strong> un nuovo thre<strong>ad</strong>, può<br />
controllare ed usufruire dei thre<strong>ad</strong> del deposito. Questi thre<strong>ad</strong>, essendo sospesi e<br />
non in esecuzione (running), sono detti re<strong>ad</strong>y.<br />
Essendo i thre<strong>ad</strong> un'importante risorsa del sistema, risulta d'altra parte sconveniente<br />
che questi, una volta creati, possano rimanere inattivi indefinitamente: <strong>per</strong> questa<br />
ragione <strong>ad</strong> ogni thre<strong>ad</strong> <strong>di</strong>venuto re<strong>ad</strong>y è assegnata una de<strong>ad</strong>line temporale entro la<br />
quale deve essere riutilizzato, o verrà eliminato. Tale limite può essere fissato <strong>per</strong><br />
tutta la classe ed anche mo<strong>di</strong>ficato a tempo d'esecuzione (con vali<strong>di</strong>tà a partire dai<br />
thre<strong>ad</strong> inseriti successivamente), e può variare il comportamento della classe<br />
dall'imme<strong>di</strong>ata <strong>di</strong>struzione dei thre<strong>ad</strong> non più attivi, fino al deposito a tempo<br />
indeterminato; questa particolare funzionalità è stata realizzata includendo nel<br />
Re<strong>ad</strong>yThre<strong>ad</strong>sManager un'istanza della classe De<strong>ad</strong>line [par. 5.5.2 De<strong>ad</strong>line].<br />
La presenza <strong>di</strong> azioni temporizzate (nella fattispecie la <strong>di</strong>struzione <strong>di</strong> thre<strong>ad</strong>) richiede,<br />
come ovvio, l'utilizzo del TimerDispatcher della piattaforma e delle strutture dati<br />
correlate [par. 5.5.1 AssociationTimerObject]: la classe implementa l'interfaccia<br />
TimerListener e si può notare la presenza <strong>di</strong> meto<strong>di</strong> <strong>per</strong> l'assegnazione <strong>di</strong> un<br />
TimerDispatcher.
Implementazione <strong>di</strong> riferimento - Thre<strong>ad</strong>Dispatcher 78<br />
Come già accennato precedentemente, al momento dell'inserimento <strong>di</strong> un thre<strong>ad</strong> tra i<br />
re<strong>ad</strong>y, tramite il metodo <strong>ad</strong>d(), a questo viene assegnata la de<strong>ad</strong>line attuale; inoltre,<br />
<strong>per</strong> far parte del deposito, l'istanza <strong>di</strong> EmbeddedThre<strong>ad</strong> deve trovarsi nello stato<br />
CREATED o SUSPENDED [Riqu<strong>ad</strong>ro 9].<br />
public synchronized boolean <strong>ad</strong>d(EmbeddedThre<strong>ad</strong> et)<br />
{<br />
if(et==null) throw new IllegalArgumentException();<br />
int thre<strong>ad</strong>State=et.getThre<strong>ad</strong>State();<br />
if(thre<strong>ad</strong>State!=EmbeddedThre<strong>ad</strong>.CREATED &&<br />
thre<strong>ad</strong>State!=EmbeddedThre<strong>ad</strong>.ABOUT_TO_SUSPEND &&<br />
thre<strong>ad</strong>State!=EmbeddedThre<strong>ad</strong>.SUSPENDED)<br />
throw new IllegalArgumentException();<br />
if(thre<strong>ad</strong>s.contains(et)) throw new IllegalArgumentException();<br />
boolean result;<br />
if(thre<strong>ad</strong>State==EmbeddedThre<strong>ad</strong>.ABOUT_TO_SUSPEND)<br />
result=et.suspend(De<strong>ad</strong>line.NEVER);<br />
else result=true;<br />
if(result)<br />
{<br />
long expiration=de<strong>ad</strong>line.getExpiration();<br />
if(expiration!=De<strong>ad</strong>line.NOW)<br />
{<br />
et.setBehaviour(null);<br />
thre<strong>ad</strong>s.<strong>ad</strong>d(et);<br />
if(theTimerDispatcher!=null && expiration!=De<strong>ad</strong>line.NEVER)<br />
{<br />
Timer t=new TimerTestingImpl(System.currentTimeMillis()+<br />
expiration,this);<br />
t=theTimerDispatcher.<strong>ad</strong>d(t);<br />
pen<strong>di</strong>ngTimers.<strong>ad</strong>dPair(et,t);<br />
}<br />
}<br />
else et.stop();<br />
}<br />
return result;<br />
}<br />
Riqu<strong>ad</strong>ro 9 : Thre<strong>ad</strong>DIspatcher::Re<strong>ad</strong>yThre<strong>ad</strong>sManager.<strong>ad</strong>d()<br />
Per ottenere un thre<strong>ad</strong> dal deposito si <strong>ad</strong>o<strong>per</strong>a il metodo get(), che restituisce (senza<br />
eliminarlo dalla lista) l'EmbeddedThre<strong>ad</strong> con sc<strong>ad</strong>enza più prossima [Riqu<strong>ad</strong>ro 10];<br />
l'eliminazione dalla lista è demandata a remove(). Sono forniti, inoltre, due meto<strong>di</strong> <strong>per</strong><br />
terminare i thre<strong>ad</strong> inseriti nel caso servisse liberare risorse: stopOne() ne elimina uno
Implementazione <strong>di</strong> riferimento - Thre<strong>ad</strong>Dispatcher 79<br />
<strong>per</strong> chiamata partendo da quello con de<strong>ad</strong>line più prossima, mentre stopAll() li<br />
elimina tutti.<br />
public synchronized EmbeddedThre<strong>ad</strong> get()<br />
{<br />
EmbeddedThre<strong>ad</strong> theThre<strong>ad</strong>=null;<br />
if(thre<strong>ad</strong>s.size()>0)<br />
{<br />
if(pen<strong>di</strong>ngTimers.size()>0)<br />
theThre<strong>ad</strong>=(EmbeddedThre<strong>ad</strong>)pen<strong>di</strong>ngTimers.getNextToExpire();<br />
else<br />
theThre<strong>ad</strong>=(EmbeddedThre<strong>ad</strong>)thre<strong>ad</strong>s.get(0);<br />
}<br />
return theThre<strong>ad</strong>;<br />
}<br />
Riqu<strong>ad</strong>ro 10 : Thre<strong>ad</strong>Dispatcher::Re<strong>ad</strong>yThre<strong>ad</strong>sManager.get()<br />
5.2.6 RequestsManager<br />
(from Thre<strong>ad</strong>Dispatcher)<br />
RequestsManager<br />
+ <strong>ad</strong>d(o : Object) : boolean<br />
+ thereIsARe<strong>ad</strong>yScheduler() : boolean<br />
+ thereIsAPen<strong>di</strong>ngThre<strong>ad</strong>() : boolean<br />
+ thereIsARe<strong>ad</strong>yThre<strong>ad</strong>() : boolean<br />
+ getRe<strong>ad</strong>yScheduler() : Scheduler<br />
+ getPen<strong>di</strong>ngThre<strong>ad</strong>() : EmbeddedThre<strong>ad</strong><br />
+ getRe<strong>ad</strong>yThre<strong>ad</strong>() : EmbeddedThre<strong>ad</strong><br />
+ remove(o : Object) : boolean<br />
+ removeAll()<br />
+ contains(o : Object) : boolean<br />
Diagramma 33 : UML Class Diagram <strong>di</strong> Thre<strong>ad</strong>Dispatcher::RequestsManager<br />
La classe RequestsManager <strong>di</strong> Thre<strong>ad</strong>Dispatcher ha il compito <strong>di</strong> fornire, all'istanza<br />
<strong>di</strong> Thre<strong>ad</strong>Dispatcher che la contiene, una serie <strong>di</strong> agevolazioni <strong>per</strong> la gestione delle
Implementazione <strong>di</strong> riferimento - Thre<strong>ad</strong>Dispatcher 80<br />
richieste <strong>di</strong> o<strong>per</strong>azioni che provengono alla classe contenitrice sia dai thre<strong>ad</strong><br />
incapsulati in EmbeddedThre<strong>ad</strong>, sia dalle istanze <strong>di</strong> Scheduler che si affidano <strong>ad</strong><br />
essa <strong>per</strong> l'esecuzione dei Behaviour a loro affidati. Compito <strong>di</strong> Thre<strong>ad</strong>Dispatcher è,<br />
infatti, quello <strong>di</strong> far incontrare richieste ed offerte provenienti dalle suddette classi.<br />
La classe, <strong>per</strong>ò, non è un semplice contenitore <strong>di</strong> richieste, ma si occupa <strong>di</strong> parte<br />
dell'elaborazione, soprattutto <strong>di</strong> quella mirata a filtrare quelle richieste <strong>per</strong> le quali non<br />
è possibile l'incontro tra domanda ed offerta.<br />
La classe o<strong>per</strong>a su due fronti:<br />
1. Richieste, da parte <strong>di</strong> oggetti EmbeddedThre<strong>ad</strong>, <strong>di</strong> ricevere un nuovo Behaviour<br />
da eseguire, avendo terminato l'action() del precedente.<br />
2. Notifiche, da parte <strong>di</strong> uno o più Scheduler, che questi hanno a <strong>di</strong>sposizione (al<br />
momento) dei Behaviour pronti <strong>per</strong> essere schedulati.<br />
Per quanto riguarda il primo tipo <strong>di</strong> chiamata, la classe si occupa <strong>di</strong> gestire solo<br />
l'eventuale nuova assegnazione, in quanto la corretta gestione della terminazione del<br />
Behaviour precedente è affidata interamente al Thre<strong>ad</strong>Dispatcher.
Implementazione <strong>di</strong> riferimento - Thre<strong>ad</strong>Dispatcher 81<br />
Un thre<strong>ad</strong> che ha appena terminato l'esecuzione <strong>di</strong> un Behaviour può, in con<strong>di</strong>zioni<br />
normali, essere imme<strong>di</strong>atamente <strong>di</strong>sponibile <strong>per</strong> l'impiego con un nuovo compito, ma<br />
in alcuni casi è necessario o<strong>per</strong>are <strong>di</strong>versamente:<br />
1. Nell'intervallo temporale tra la precedente assegnazione e la nuova richiesta, il<br />
pool a <strong>di</strong>sposizione del Thre<strong>ad</strong>Dispatcher è stato ri<strong>di</strong>mensionato ed i thre<strong>ad</strong><br />
attualmente in esecuzione eccedono la nuova capacità: dopo il loro corretto<br />
ritorno non sono più necessari e devono essere eliminati. I thre<strong>ad</strong> così scremati<br />
vengono definiti "pen<strong>di</strong>ngThre<strong>ad</strong>" in quanto sono pronti all'esecuzione ed in<br />
attesa <strong>di</strong> un'eventuale assegnazione <strong>di</strong> Behaviour [Riqu<strong>ad</strong>ro 11].<br />
public synchronized boolean thereIsAPen<strong>di</strong>ngThre<strong>ad</strong>()<br />
{<br />
while(behaviourRequestsQueue.size()>0)<br />
{<br />
EmbeddedThre<strong>ad</strong> theThre<strong>ad</strong>=<br />
(EmbeddedThre<strong>ad</strong>)behaviourRequestsQueue.get(0);<br />
if(poolCapacity==SINGLE_THREADED ||<br />
(runningThre<strong>ad</strong>s.size()>poolCapacity &&<br />
poolCapacity!=UNLIMITED_POOL))<br />
removeBRAndStopT((EmbeddedThre<strong>ad</strong>)behaviourRequestsQueue.get(0));<br />
else break;<br />
}<br />
return (behaviourRequestsQueue.size()>0);<br />
}<br />
Riqu<strong>ad</strong>ro 11 : Thre<strong>ad</strong>Dispatcher::RequestsManager.thereIsAPen<strong>di</strong>ngThre<strong>ad</strong>()<br />
2. Se l'assegnazione non può essere portata a termine (<strong>per</strong>ché al momento non vi<br />
sono Scheduler con compiti <strong>di</strong>sponibili), i pen<strong>di</strong>ngThre<strong>ad</strong> vengono comunque<br />
mantenuti attivi <strong>per</strong> utilizzi futuri ed affidati al Re<strong>ad</strong>yThre<strong>ad</strong>sManager [par. 5.2.5<br />
Re<strong>ad</strong>yThre<strong>ad</strong>sManager]. Quando, al contrario, un thre<strong>ad</strong> trova uno Scheduler<br />
<strong>di</strong>sponibile è effettivamente pronto <strong>per</strong> l'esecuzione ed è detto (in questo<br />
contesto) "re<strong>ad</strong>yThre<strong>ad</strong>".<br />
Per quanto concerne le richieste provenienti dagli Scheduler, la con<strong>di</strong>zione normale<br />
prevederebbe che, una volta esaminata la richiesta il Thre<strong>ad</strong>Dispatcher procedesse<br />
<strong>di</strong>rettamente alle o<strong>per</strong>azioni <strong>di</strong> scheduling, sennonché il meccanismo <strong>ad</strong>ottato<br />
concede allo Scheduler massima libertà <strong>di</strong> azione fino a che il Thre<strong>ad</strong>Dispatcher non
Implementazione <strong>di</strong> riferimento - Thre<strong>ad</strong>Dispatcher 82<br />
effettua l'effettivo controllo sulla <strong>di</strong>sponibilità <strong>di</strong> Behaviour dello Scheduler stesso: se,<br />
nel lasso <strong>di</strong> tempo intercorrente tra la segnalazione, da parte dello Scheduler, della<br />
presenza <strong>di</strong> Behaviour da schedulare ed il controllo della loro effettiva <strong>di</strong>sponibilità,<br />
dovessero cambiare le con<strong>di</strong>zioni dello Scheduler (Es. se il Behaviour pronto <strong>per</strong><br />
l'esecuzione venisse cancellato e non ve ne fossero altri), allora la richiesta <strong>di</strong><br />
scheduling potrebbe essere infondata e dovrebbe essere scartata. Gli Scheduler<br />
restanti vengono designati come "re<strong>ad</strong>yScheduler" [Riqu<strong>ad</strong>ro 12].<br />
public synchronized boolean thereIsARe<strong>ad</strong>yScheduler()<br />
{<br />
while(schedulingRequestsQueue.size()>0)<br />
{<br />
Scheduler theScheduler=<br />
(Scheduler)schedulingRequestsQueue.get(0);<br />
if(theScheduler.hasNothingToSchedule())<br />
removeSchedulingRequest(theScheduler);<br />
else break;<br />
}<br />
return (schedulingRequestsQueue.size()>0);<br />
}<br />
Riqu<strong>ad</strong>ro 12 : Thre<strong>ad</strong>Dispatcher::RequestsManager.threreIsARe<strong>ad</strong>yScheduler()<br />
Si ricorda che la politica <strong>ad</strong>ottata <strong>per</strong> lo Scheduler prevede che un Behaviour <strong>di</strong>venti<br />
"running" (e quin<strong>di</strong> certo <strong>di</strong> una sua esecuzione a meno <strong>di</strong> eventi catastrofici)<br />
unicamente a seguito <strong>di</strong> una chiamata <strong>di</strong>retta a schedule() o <strong>ad</strong><br />
hasNothingToSchedule() [par. 5.3.5 Scheduling: notifiche, ispezioni, richieste e<br />
terminazione].<br />
I meto<strong>di</strong> riguardanti le richieste <strong>di</strong> scheduling si occupano inoltre <strong>di</strong> notificare allo<br />
Scheduler richiedente l'avvenuta presa in considerazione (o meno) della loro<br />
chiamata: questo evita possibili blocchi dello Scheduler dovuti al meccanismo <strong>di</strong><br />
notifica <strong>ad</strong>ottato [par. 5.3.5.2 Richieste <strong>di</strong> scheduling].<br />
Infine i meto<strong>di</strong> remove() e removeAll() sono stati inseriti <strong>per</strong> essere usati in fasi<br />
collegate con il blocco delle attività del Thre<strong>ad</strong>Dispatcher: eliminano una o più<br />
richieste mantenendo la coerenza dello stato degli oggetti client.
Implementazione <strong>di</strong> riferimento - Scheduler 83<br />
5.3 Scheduler<br />
5.3.1 Scopi e funzionalità<br />
Lo scheduler è l'entità preposta alla selezione della sequenza d'esecuzione dei<br />
Behaviour.<br />
ParallelScheduler<br />
Amministra il loro life-cycle occupandosi <strong>di</strong> collocarli opportunamente in liste interne<br />
che ne rispecchiano lo stato e gestendo <strong>di</strong>rettamente l'esecuzione <strong>di</strong> azioni quali<br />
block(),restart() ed affini. Dialoga da un lato con l'utente (o chi <strong>per</strong> lui) che gli da in<br />
consegna i Behaviour o ne richiede l'eliminazione, dall'altro con il Thre<strong>ad</strong>Dispatcher<br />
che ne chiama le funzioni <strong>di</strong> scheduling. È <strong>di</strong>pendente dal sistema run-time <strong>di</strong> JADE<br />
<strong>per</strong> le o<strong>per</strong>azioni <strong>di</strong> risveglio temporizzato dei Behaviour e <strong>per</strong> la notifica dell'arrivo <strong>di</strong><br />
nuovi messaggi (che causa il risveglio <strong>di</strong> tutti i Behaviour affidatigli).<br />
Le sue funzionalità possono essere sud<strong>di</strong>vise in:<br />
1. Affidamento e rimozione <strong>di</strong> Behaviour<br />
2. Blocco e risveglio <strong>di</strong> Behaviour<br />
Scheduler<br />
SequentialScheduler FSMScheduler<br />
Diagramma 34 : UML Class Diagram <strong>di</strong> Scheduler e classi derivate<br />
3. Scheduling: notifiche, ispezioni, richieste e terminazione<br />
4. Amministrazione dei cambiamenti <strong>di</strong> stato dei Behaviour
Implementazione <strong>di</strong> riferimento - Scheduler 84<br />
5. Cambiamenti <strong>di</strong> stato imposti ai Behaviour dall'esterno<br />
5.3.2 La classe astratta<br />
Diagramma 35 : UML Class Diagram <strong>di</strong> Scheduler<br />
Scheduler<br />
+ Scheduler(td : TimerDispatcher) : Scheduler<br />
+ Scheduler(cb : CompositeBehaviour) : Scheduler<br />
+ <strong>ad</strong>d(b : Behaviour) : boolean<br />
+ remove(b : Behaviour) : boolean<br />
+ removeAll(b : Behaviour) : boolean<br />
+ restartLater(b : Behaviour, millis : long)<br />
+ notifyRestarted(b : Behaviour)<br />
+ doTimeout(t : Timer)<br />
+ notifySchedulingRequestProcessed()<br />
+ hasNothingToSchedule() : boolean<br />
# check() : boolean<br />
+ schedule() : Behaviour<br />
# choose() : Behaviour<br />
+ notifyExecuted(b : Behaviour)<br />
+ notifyInterrupted(b : Behaviour)<br />
+ restartAll()<br />
+ reset(expiration : long)<br />
+ hasTerminated() : boolean<br />
Le suddette funzioni, comuni <strong>ad</strong> ogni tipologia <strong>di</strong> scheduler, sono state implementate<br />
in una classe base astratta, priva <strong>di</strong> politiche decisionali <strong>per</strong> la scelta dell'or<strong>di</strong>ne e<br />
delle modalità d'attivazione dei Behaviour. Le sue sottoclassi concrete hanno il<br />
compito <strong>di</strong> colmare la lacuna implementando specifici meto<strong>di</strong>.<br />
5.3.3 Affidamento e rimozione <strong>di</strong> Behaviour<br />
L'utente che desidera l'esecuzione <strong>di</strong> un dato Behaviour, precedentemente<br />
istanziato, da parte <strong>di</strong> un agente, passa a questo un puntatore a tale oggetto. L'Agent<br />
a sua volta inserisce ed affida il Behaviour al suo top-Scheduler che si occupa <strong>di</strong>
Implementazione <strong>di</strong> riferimento - Scheduler 85<br />
gestirlo ed interagire con il Thre<strong>ad</strong>Dispatcher opportuno <strong>per</strong> la sua effettiva<br />
esecuzione.<br />
Il metodo <strong>ad</strong>d() consente questo tipo <strong>di</strong> affidamento. Lo scheduler controlla innanzi<br />
tutto lo stato del Behaviour passatogli e lo memorizza nell'opportuna lista [Riqu<strong>ad</strong>ro<br />
28].<br />
public synchronized boolean <strong>ad</strong>d(Behaviour b)<br />
{<br />
boolean result=false;<br />
if(result=!b.done())<br />
{<br />
pushIn(b);<br />
if(b.isRunnable()) <strong>ad</strong>dToRe<strong>ad</strong>yBehaviours(b);<br />
else blockedBehaviours.<strong>ad</strong>d(b);<br />
}<br />
return result;<br />
}<br />
Riqu<strong>ad</strong>ro 13 : Scheduler.<strong>ad</strong>d()<br />
Uno scheduler incapsula al suo interno tre liste <strong>di</strong> Behaviour <strong>per</strong> <strong>di</strong>stinguerne ed<br />
amministrarne lo stato; vi è inoltre un contatore dei Behaviour terminati <strong>per</strong> la<br />
determinazione <strong>di</strong> particolari con<strong>di</strong>zioni <strong>di</strong> terminazione dello scheduler stesso.<br />
Un Behaviour, dal punto <strong>di</strong> vista dello scheduler, può essere:<br />
1. Re<strong>ad</strong>y, quando, con riferimento a meto<strong>di</strong> proprietari <strong>di</strong> un Behaviour,<br />
isRunnable()==true, done()==false e risulta quin<strong>di</strong> pronto <strong>per</strong> una possibile<br />
esecuzione.<br />
2. Running, quando un re<strong>ad</strong>y viene schedulato e riservato da un Thre<strong>ad</strong>Dispatcher<br />
<strong>per</strong> l'esecuzione. Il Behaviour rimane in questo stato da quando viene "prenotato<br />
<strong>per</strong> lo scheduling" fino al suo ritorno da una sessione d'esecuzione (che può<br />
implicare una completa esecuzione su un thre<strong>ad</strong>, la sua interruzione durante<br />
l'esecuzione, o l'arrivo <strong>di</strong> particolari richieste ancora prima che il Behaviour sia<br />
effettivamente passato al Thre<strong>ad</strong>Dispatcher).<br />
3. Blocked, quando isRunnable()==false.<br />
4. Terminated, quando done()==true.
Implementazione <strong>di</strong> riferimento - Scheduler 86<br />
Si ricorda che lo stato <strong>di</strong> un Behaviour è esaminato o prima della sua esecuzione, o<br />
imme<strong>di</strong>atamente dopo il suo ritorno da una sessione d'esecuzione, ma non durante:<br />
una chiamata a Behaviour.block() all'interno <strong>di</strong> action() non ha effetto imme<strong>di</strong>ato, ma<br />
viene eseguita appena si ha un ritorno del controllo.<br />
L'inserimento <strong>di</strong> un nuovo Behaviour in uno scheduler implica una serie <strong>di</strong> o<strong>per</strong>azioni<br />
d'amministrazione e regolazione <strong>di</strong> parametri del Behaviour stesso che è nascosta al<br />
progettista, quali passaggio <strong>di</strong> riferimenti allo scheduler amministrante e, <strong>per</strong> i<br />
CompositeBehaviour, passaggio <strong>di</strong> riferimenti al sistema run-time, Thre<strong>ad</strong>Dispatcher<br />
e gestione della gerarchia <strong>di</strong> Behaviour.<br />
5.3.3.1 Behaviour duplicati<br />
Uno scheduler-base consente che gli siano affidati duplicati <strong>di</strong> Behaviour che già<br />
contiene nelle sue liste: se istanzio b1 e lo aggiungo più volte allo scheduler, questo<br />
mantiene separati i <strong>di</strong>fferenti riferimenti all'interno delle sue strutture, come se si<br />
trattasse <strong>di</strong> istanze <strong>di</strong>verse.<br />
Questa possibilità è stata lasciata <strong>per</strong> scopi particolari del SequentialScheduler:<br />
inserendo più riferimenti allo stesso Behaviour in tale Scheduler, e facendo in modo<br />
che il suo metodo onEnd() chiami reset(), si possono creare ripetizioni controllate<br />
dello stesso comportamento.<br />
I riferimenti duplicati <strong>per</strong> uno stesso Behaviour devono essere amministrati<br />
<strong>ad</strong>eguatamente: dal momento che si riferiscono allo stesso oggetto, devono tutti<br />
seguirne l'evoluzione, inoltre lo scheduler deve evitare <strong>di</strong> schedulare un riferimento<br />
duplicato mentre ve n'è già un altro in esecuzione.<br />
Ipotizzando una situazione iniziale in cui tutti i riferimenti siano nella lista dei re<strong>ad</strong>y, lo<br />
scheduling <strong>di</strong> uno <strong>di</strong> questi comporta il suo passaggio tra i running (ed il vincolo <strong>per</strong><br />
gli altri a non poter essere eseguiti); se al ritorno dall'esecuzione il Behaviour <strong>di</strong>venta<br />
blocked, tutti i suoi duplicati vengono spostati <strong>di</strong> conseguenza; quando un blocked<br />
duplicato viene riattivato, tutte le copie vengono trasferite nuovamente tra i re<strong>ad</strong>y; se<br />
al suo ritorno da un'esecuzione il behaviour ha terminato (e non esegue nel corpo <strong>di</strong><br />
onEnd() chiamate che ne ripristino lo stato a runnable) e deve lasciare lo scheduler,<br />
tutti i duplicati devono fare lo stesso.
Implementazione <strong>di</strong> riferimento - Scheduler 87<br />
Gli stati possibili <strong>per</strong> la collocazione dei duplicati all'interno delle liste risultano quin<strong>di</strong>:<br />
?? Tutti i duplicati sono tra i re<strong>ad</strong>y<br />
?? Al più un duplicato è tra i running ed i restanti sono tra i re<strong>ad</strong>y<br />
?? Tutti i duplicati sono tra i blocked<br />
Per quanto riguarda la rimozione <strong>di</strong> Behaviour da uno scheduler, vista la presenza <strong>di</strong><br />
duplicati, esistono due meto<strong>di</strong> <strong>di</strong>stinti: remove(), che elimina un solo riferimento del<br />
Behaviour che gli è passato come parametro, e removeAll() che li elimina tutti<br />
(scheduler specifici, come SequentialScheduler ne affiancano ulteriori) [Riqu<strong>ad</strong>ro 14].<br />
public synchronized boolean removeAll(Behaviour b)<br />
{<br />
boolean result=contains(b);<br />
if(result)<br />
{<br />
if(runningBehaviours.contains(b))<br />
{<br />
if(!removedWhileRunningBehaviours.contains(b))<br />
removedWhileRunningBehaviours.<strong>ad</strong>d(b);<br />
while(re<strong>ad</strong>yBehaviours.remove(b));<br />
}<br />
else<br />
{<br />
if(blockedBehaviours.remove(b))<br />
{<br />
while(blockedBehaviours.remove(b));<br />
removePen<strong>di</strong>ngTimer(b);<br />
}<br />
else<br />
while(re<strong>ad</strong>yBehaviours.remove(b));<br />
pullOut(b);<br />
}<br />
}<br />
return result;<br />
}<br />
Riqu<strong>ad</strong>ro 14 : Scheduler.removeAll()<br />
5.3.4 Blocco e risveglio <strong>di</strong> Behaviour<br />
Quando all'interno <strong>di</strong> una action() un Behaviour invoca il metodo block(), al suo<br />
ritorno dal quanto d'esecuzione, è lo scheduler che si occupa <strong>di</strong> spostare il suo<br />
riferimento nella lista dei blocked ed amministrarne il risveglio.
Implementazione <strong>di</strong> riferimento - Scheduler 88<br />
Se si è trattato <strong>di</strong> una sospensione temporizzata, tramite chiamata <strong>di</strong> block(millis), dal<br />
corpo del metodo stesso, viene invocato restartLater() dello scheduler che,<br />
interagendo con il TimerDispatcher del sistema run-time dell'Agent, pre<strong>di</strong>spone un<br />
timer e tiene traccia del fatto. La sc<strong>ad</strong>enza del timer innescato provocherà poi<br />
l'esecuzione del metodo doTimeout() che si occu<strong>per</strong>à del risveglio. Se il Behaviour<br />
viene riattivato <strong>per</strong> altre cause, un'invocazione <strong>di</strong> notifyRestarted() provvederà al<br />
necessario passaggio <strong>di</strong> lista ed alla rimozione del timer pendente.<br />
5.3.5 Scheduling: notifiche, ispezioni, richieste e terminazione<br />
Il meccanismo <strong>ad</strong>ottato <strong>per</strong> lo scheduling consiste in due meto<strong>di</strong> principali:<br />
hasNothingToSchedule() e schedule(). Il primo effettua un controllo e restituisce true<br />
se lo scheduler non ha Behaviour da eseguire (<strong>per</strong> essere più precisi, restituisce true<br />
solo se un'analoga chiamata a schedule() ritorna null), il secondo ritorna un<br />
riferimento al Behaviour da eseguire o null se non vi sono le con<strong>di</strong>zioni <strong>per</strong><br />
l'esecuzione.<br />
I vincoli <strong>per</strong> la restituzione <strong>di</strong> un riferimento valido <strong>ad</strong> un Behaviour sono <strong>di</strong>versi.<br />
Scheduler restituisce null se:<br />
?? La lista dei Behaviour re<strong>ad</strong>y è vuota<br />
?? La lista dei re<strong>ad</strong>y non è vuota, ma vincoli <strong>di</strong> sequenzialità impe<strong>di</strong>scono che un<br />
Behaviour v<strong>ad</strong>a in esecuzione prima che il precedente sia ritornato(<br />
SequentialScheduler)<br />
?? La lista non è vuota, ma vi sono solo riferimenti duplicati <strong>ad</strong> un Behaviour già in<br />
esecuzione<br />
?? Lo scheduler sta effettuando un'o<strong>per</strong>azione <strong>di</strong> reset [par. 5.3.7 Cambiamenti <strong>di</strong><br />
stato imposti ai Behaviour dall'esterno] e nuovi Behaviour non possono essere<br />
eseguiti prima che lo stato interno ritorni consistente<br />
?? Lo scheduler ha raggiunto la con<strong>di</strong>zione <strong>di</strong> terminazione (che <strong>di</strong>pende dalla<br />
sottoclasse concreta)<br />
Il metodo hasNothingToSchedule() non implementa un semplice controllo, ma<br />
effettua anche una sorta <strong>di</strong> "prenotazione" del Behaviour. Una chiamata al metodo
Implementazione <strong>di</strong> riferimento - Scheduler 89<br />
comporta, infatti, l'invocazione <strong>di</strong> schedule() e la memorizzazione, in una variabile<br />
interna, del prossimo Behaviour da schedulare che sarà passato all'invocante<br />
(solitamente il Thre<strong>ad</strong>Dispatcher) al successivo ricorso a schedule() [Riqu<strong>ad</strong>ro 15]<br />
[Riqu<strong>ad</strong>ro 16].<br />
public synchronized boolean hasNothingToSchedule()<br />
{<br />
if(nextToSchedule==null)<br />
nextToSchedule=schedule();<br />
return (nextToSchedule==null);<br />
}<br />
Riqu<strong>ad</strong>ro 15 : Scheduler.hasNothingToSchedule()<br />
Questo sistema <strong>per</strong>mette <strong>di</strong> incapsulare tutte le politiche relative alla <strong>di</strong>sponibilità <strong>di</strong><br />
Behaviour <strong>per</strong> l'esecuzione all'interno del metodo schedule() evitando <strong>di</strong> duplicare<br />
controlli in hasNothingToSchedule(). In realtà schedule si occupa solo della gestione<br />
del Behaviour prescelto <strong>per</strong> l'esecuzione, ma la scelta è effettuata dal metodo<br />
protected choose() che ogni sottoclasse concreta deve implementare.
Implementazione <strong>di</strong> riferimento - Scheduler 90<br />
Una volta che choose() ha scelto il Behaviour, schedule() provvede a spostarlo tra i<br />
running e <strong>ad</strong> effettuare eventuali o<strong>per</strong>azioni <strong>di</strong> inizializzazione specificate nel suo<br />
metodo onStart() (previa ispezione <strong>di</strong> yetStarted() che ritorna true se il Behaviour non<br />
ha ancora eseguito la sua prima action()) [Riqu<strong>ad</strong>ro 16].<br />
public synchronized Behaviour schedule()<br />
{<br />
Behaviour b=null;<br />
if(!hasTerminated())<br />
{<br />
if(nextToSchedule!=null)<br />
{<br />
b=nextToSchedule;<br />
nextToSchedule=null;<br />
}<br />
else<br />
{<br />
if(!resetting)<br />
if((b=choose())!=null)<br />
{<br />
re<strong>ad</strong>yBehaviours.remove(b);<br />
runningBehaviours.<strong>ad</strong>d(b);<br />
if(!b.yetStarted()) b.onStart();<br />
}<br />
}<br />
}<br />
return b;<br />
}<br />
Riqu<strong>ad</strong>ro 16 : Scheduler.schedule()<br />
5.3.5.1 Ispezioni non mo<strong>di</strong>ficative<br />
Quando il metodo schedule() viene chiamato (<strong>di</strong>rettamente od in<strong>di</strong>rettamente tramite<br />
hasNothingToSchedule()), e con lui eventualmente choose(), queste chiamate<br />
possono comportare mo<strong>di</strong>fiche allo stato interno dello scheduler: il puntatore al<br />
prossimo Behaviour da schedulare può venire aggiornato e lo scheduler crede che il<br />
Behaviour restituito dalla chiamata sia in procinto <strong>di</strong> essere eseguito.<br />
Queste mo<strong>di</strong>fiche possono essere indesiderate se si vuole solamente controllare la<br />
"<strong>di</strong>sponibilità" <strong>di</strong> Behaviour; nasce quin<strong>di</strong> la necessità <strong>di</strong> introdurre meto<strong>di</strong> interni che<br />
effettuino tale verifica senza apportare mo<strong>di</strong>fiche ai puntatori delle liste. Questi
Implementazione <strong>di</strong> riferimento - Scheduler 91<br />
meto<strong>di</strong>, sviluppati <strong>per</strong> scopi interni [par. 5.3.5.2 Richieste <strong>di</strong> scheduling], dovrebbero<br />
essere nascosti all'utilizzatore, ma, avendo separato le politiche decisionali all'interno<br />
<strong>di</strong> choose(), <strong>di</strong> possibile competenza del programmatore, e potendo essere<br />
anch'esso mo<strong>di</strong>ficativo, si è reso necessario introdurre il metodo astratto check() con<br />
gli stessi scopi <strong>di</strong> choose(), ma non mo<strong>di</strong>ficativo, che deve essere implementato dalle<br />
sottoclassi.<br />
5.3.5.2 Richieste <strong>di</strong> scheduling<br />
Uno scheduler, <strong>per</strong> l'esecuzione dei Behaviour, è strettamente <strong>di</strong>pendente da un<br />
Thre<strong>ad</strong>Dispatcher. È questo oggetto che svolge un ruolo attivo nella comunicazione,<br />
interrogando lo scheduler sulla <strong>di</strong>sponibilità <strong>di</strong> Behaviour, utilizzando<br />
hasNothingToSchedule()) ed eventualmente richiedendoli attraverso schedule().<br />
Il metodo hasNothingToSchedule(), come già detto, effettua una prenotazione del<br />
Behaviour, che agli occhi dello scheduler appare già running anche se sarà<br />
consegnato al Thre<strong>ad</strong>Dispatcher solo dopo una chiamata a schedule().<br />
Questo protocollo è stato <strong>ad</strong>ottato <strong>per</strong> evitare che lo scheduler cambiasse stato tra<br />
un'interrogazione e la successiva richiesta. Se hasNothingToSchedule() non<br />
effettuasse la prenotazione, spostando il Behaviour tra i running, un remove() che si<br />
inserisse tra la sua chiamata e quella a schedule() potrebbe eliminare il Behaviour<br />
re<strong>ad</strong>y e lasciare il Thre<strong>ad</strong>Dispatcher, precedentemente convinto del contrario, senza<br />
compito.<br />
Il meccanismo delle richieste è complicato anche dal fatto che un Thre<strong>ad</strong>Dispatcher<br />
può gestire più <strong>di</strong> uno scheduler e quin<strong>di</strong> non è legato <strong>ad</strong> uno in particolare. Per<br />
questo motivo, non è il Thre<strong>ad</strong>Dispatcher che interroga <strong>di</strong>rettamente il suo <strong>per</strong>sonale<br />
scheduler, ma ogni scheduler, legato <strong>ad</strong> un solo Thre<strong>ad</strong>Dispatcher, gli segnala<br />
quando dovrebbe essere ispezionato.<br />
Il protocollo è il seguente:<br />
1. Ogni scheduler, dopo che un'o<strong>per</strong>azione potrebbe averne mo<strong>di</strong>ficato lo stato,<br />
controlla la propria <strong>di</strong>sponibilità <strong>di</strong> Behaviour (usando i meto<strong>di</strong> <strong>per</strong> le ispezioni non<br />
mo<strong>di</strong>ficative [par. 5.3.5.1 Ispezioni non mo<strong>di</strong>ficative])
Implementazione <strong>di</strong> riferimento - Scheduler 92<br />
2. Nel caso vi siano Behaviour pronti <strong>per</strong> essere schedulati, e non si sia già<br />
segnalata la cosa, si provvede <strong>ad</strong> avvisare il Thre<strong>ad</strong>Dispatcher [par. 5.3.5.2<br />
Richieste <strong>di</strong> scheduling]<br />
3. Il Thre<strong>ad</strong>Dispatcher, processata la richiesta, effettua il suddetto ciclo <strong>di</strong> ispezione<br />
ed acquisizione del Behaviour.<br />
Questo protocollo evita che il Thre<strong>ad</strong>Dispatcher debba conoscere esplicitamente, e<br />
mantenere una lista, degli scheduler che si appoggiano <strong>ad</strong> esso, e debba effettuare<br />
cicli attivi <strong>di</strong> interrogazioni su <strong>di</strong> essi (polling).<br />
Si vuole precisare, <strong>per</strong>ò, che la richiesta d'interrogazione effettuata dallo scheduler,<br />
non implica che, al momento in cui questa sarà effettuata, lo scheduler abbia ancora<br />
Behaviour <strong>di</strong>sponibili: nel tempo intercorrente tra segnalazione ed ispezione lo<br />
scheduler è libero <strong>di</strong> cambiare stato ed è quin<strong>di</strong> compito del Thre<strong>ad</strong>Dispatcher filtrare<br />
queste "richieste a vuoto" [par. 5.2.6 RequestsManager].<br />
Quando un Thre<strong>ad</strong>Dispatcher processa una richiesta <strong>di</strong> uno scheduler, deve inoltre<br />
segnalare a questo <strong>di</strong> averlo fatto, <strong>per</strong> dare la possibilità che nuove auto-ispezioni<br />
possano generare le opportune notifiche [punto 2]: questo avviene usando il metodo<br />
notifySchedulingRequestProcessed().<br />
5.3.5.3 Terminazione del compito <strong>di</strong> uno scheduler<br />
Uno scheduler può avere assegnata una con<strong>di</strong>zione <strong>di</strong> terminazione che varia a<br />
seconda del tipo specifico <strong>di</strong> sottoclasse implementata: un SequentialScheduler, <strong>ad</strong><br />
esempio, termina quando ha finito <strong>di</strong> schedulare tutta la lista dei suoi Behaviour, un<br />
top-Scheduler dovrebbe terminare solo insieme all'Agent che lo possiede, etc..<br />
In Scheduler vi è un metodo astratto demandato a tale segnalazione che deve<br />
essere implementato dalle sottoclassi: hasTerminated().<br />
5.3.6 Amministrazione dei cambiamenti <strong>di</strong> stato dei Behaviour<br />
Dopo che un Behaviour è stato schedulato, viene eseguito da un thre<strong>ad</strong> del pool<br />
amministrato dal Thre<strong>ad</strong>Dispatcher, e durante l'esecuzione della sua action() può<br />
invocare meto<strong>di</strong> che ne cambiano lo stato (come block()), inoltre, la conclusione <strong>di</strong><br />
una action() può causare l'innesco <strong>di</strong> con<strong>di</strong>zioni <strong>di</strong> terminazione del Behaviour.
Implementazione <strong>di</strong> riferimento - Scheduler 93<br />
Queste mo<strong>di</strong>fiche al Behaviour si rispecchiano nei risultati dei meto<strong>di</strong> <strong>per</strong> la sua<br />
ispezione, quali isRunnable(), done(), etc., e devono essere esaminate al ritorno<br />
dalla sessione d'esecuzione.<br />
Quando un Behaviour termina correttamente la sessione, un meccanismo <strong>di</strong> notifiche<br />
attraversa la catena <strong>di</strong> responsabilità degli oggetti coinvolti (da EmbeddedThre<strong>ad</strong> a<br />
Thre<strong>ad</strong>Dispatcher fino a Scheduler) ed il metodo notifyExecuted() viene infine<br />
invocato su Scheduler.<br />
Questo metodo si occupa d'ispezionare lo stato del Behaviour e gestire una corretta<br />
terminazione della sessione d'esecuzione:<br />
?? Se done()==true esegue il metodo onEnd() del Behaviour ed eventuale rimozione<br />
dalle liste.<br />
?? Se isRunnable()==false sposta il Behaviour tra i blocked<br />
?? Se invece isRunnable()==true reinserisce il riferimento tra i re<strong>ad</strong>y<br />
Oltre a questi comportamenti <strong>di</strong> massima, il metodo notifyExecuted() gestisce una<br />
serie <strong>di</strong> variabili interne <strong>per</strong> rispecchiare l'esito dell'esecuzione del Behaviour. Queste<br />
variabili, che sono currentExecuted, currentDone, lastExitValue e<br />
terminatedBehavioursCounter, servono alle implementazioni concrete della classe<br />
<strong>per</strong> amministrare vincoli <strong>di</strong> sincronizzazione, con<strong>di</strong>zioni <strong>di</strong> terminazione e <strong>per</strong><br />
in<strong>di</strong>rizzare scelte interne relative alla sequenza <strong>di</strong> scheduling [Riqu<strong>ad</strong>ro 17].
Implementazione <strong>di</strong> riferimento - Scheduler 94<br />
public synchronized void notifyExecuted(Behaviour b)<br />
{<br />
if(runningBehaviours.remove(b))<br />
{<br />
...<br />
{<br />
if(b.isRunnable())<br />
{<br />
if(b.done())<br />
{<br />
currentDone=true;<br />
lastExitValue=b.onEnd();<br />
terminatedBehavioursCounter++;<br />
if(b.done())<br />
while(re<strong>ad</strong>yBehaviours.remove(b))<br />
terminatedBehavioursCounter++;<br />
pullOut(b);<br />
}<br />
else<br />
{<br />
currentExecuted=true;<br />
<strong>ad</strong>dToRe<strong>ad</strong>yBehaviours(b);<br />
}<br />
}<br />
else<br />
{<br />
if(restarted)<br />
{<br />
currentExecuted=true;<br />
b.restart();<br />
}<br />
else<br />
{<br />
blockedBehaviours.<strong>ad</strong>d(b);<br />
while(re<strong>ad</strong>yBehaviours.remove(b))<br />
blockedBehaviours.<strong>ad</strong>d(b);<br />
if(blockCon<strong>di</strong>tion()) owner.blockAndNotifyUp();<br />
}<br />
}<br />
}<br />
notify();<br />
checkAndRequest();<br />
}<br />
}<br />
Riqu<strong>ad</strong>ro 17 : Scheduler.notifyExecuted() amministrazione cambiamenti interni a Behaviour<br />
Altro compito svolto dal metodo è il controllo <strong>di</strong> situazioni che comportano il blocco <strong>di</strong><br />
un CompositeBehaviour: quando uno scheduler è demandato all'amministrazione dei<br />
sub-behaviour affidati <strong>ad</strong> un CompositeBehaviour, il blocco <strong>di</strong> un sub-behaviour può
Implementazione <strong>di</strong> riferimento - Scheduler 95<br />
causare l'arresto del Behaviour contenitore (<strong>per</strong>ché <strong>ad</strong> esempio tutti i Behaviour<br />
affidatigli risultano blocked), e questo deve essere segnalato.<br />
Il metodo si occupa anche <strong>di</strong> amministrare quei cambiamenti che vengono richiesti<br />
dall'esterno mentre un Behaviour è in esecuzione, quali remove(), reset() e restart(),<br />
e quin<strong>di</strong> non possono essere effettuati imme<strong>di</strong>atamente, ma devono attendere il<br />
ritorno della action() [par. 5.3.7 Cambiamenti <strong>di</strong> stato imposti ai Behaviour<br />
dall'esterno] [Riqu<strong>ad</strong>ro 18].<br />
public synchronized void notifyExecuted(Behaviour b)<br />
{<br />
if(runningBehaviours.remove(b))<br />
{<br />
currentDone=false;<br />
currentExecuted=false;<br />
boolean removed=removedWhileRunningBehaviours.remove(b);<br />
boolean resetted=resettedWhileRunningBehaviours.remove(b);<br />
boolean restarted=restartedWhileRunningBehaviours.remove(b);<br />
if(removed)<br />
{<br />
if(b.done())<br />
{<br />
currentDone=true;<br />
lastExitValue=b.onEnd();<br />
terminatedBehavioursCounter++;<br />
}<br />
else if(b.isRunnable()) currentExecuted=true;<br />
else removePen<strong>di</strong>ngTimer(b);<br />
pullOut(b);<br />
}<br />
else if(resetted)<br />
{<br />
if(b.done()) b.onEnd();<br />
b.reset();<br />
<strong>ad</strong>dToRe<strong>ad</strong>yBehaviours(b);<br />
}<br />
...<br />
}<br />
}<br />
Riqu<strong>ad</strong>ro 18 : Scheduler.notifyExecuted() amministrazione cambiamenti a Behaviour in fase<br />
d'esecuzione<br />
Questi cambiamenti sono amministrati con una politica a priorità, nel caso si<br />
verificassero più richieste contemporaneamente durante l'esecuzione del Behaviour.
Implementazione <strong>di</strong> riferimento - Scheduler 96<br />
La priorità è data nell'or<strong>di</strong>ne, cominciando dall'o<strong>per</strong>azione più "forte", a remove(),<br />
seguito da reset() e restart(): questo vuol <strong>di</strong>re che, <strong>ad</strong> esempio, se <strong>ad</strong> un Behaviour<br />
running arriva sia la richiesta <strong>di</strong> restart(), sia un remove(), il Behaviour viene<br />
comunque eliminato dallo scheduler.<br />
Oltre <strong>ad</strong> una terminazione normale della sessione d'esecuzione, lo scheduler deve<br />
far fronte correttamente anche a quelle situazioni in cui il Behaviour subisce<br />
un'interruzione. Per amministrare tale evenienza, si ricorre <strong>ad</strong> un altro tipo <strong>di</strong> notifica<br />
che funziona similmente a notifyExecuted(), ma ha necessariamente comportamenti<br />
<strong>di</strong>fferenti: il metodo è notifyInterrupted().<br />
5.3.7 Cambiamenti <strong>di</strong> stato imposti ai Behaviour dall'esterno<br />
Oltre alle mo<strong>di</strong>fiche dello stato <strong>di</strong> un Behaviour che possono avvenire durante<br />
l'esecuzione del metodo action() dello stesso, vi sono una serie <strong>di</strong> cambiamenti che<br />
possono essere innescati da eventi esterni.<br />
Mentre non si hanno problemi quando questi riguardano Behaviour che al momento<br />
della chiamata non sono in esecuzione, ma mantengono solamente il loro riferimento<br />
all'interno delle liste dello scheduler, <strong>di</strong>versa è la situazione quando tali cambiamenti<br />
devono essere notificati a Behaviour che stanno eseguendo la loro action().<br />
Come avviene con i meto<strong>di</strong> notifyExecuted() e notifyInterrupted(), le mo<strong>di</strong>fiche<br />
possono avvenire solamente al ritorno dalla sessione d'esecuzione. Per questo<br />
motivo, all'interno dello scheduler, esistono alcune liste con lo scopo <strong>di</strong> tenere traccia<br />
dei cambiamenti che sono stati richiesti esternamente su Behaviour running, in modo<br />
da poterli eseguire quanto prima: quando un Behaviour termina il proprio quanto<br />
d'azione e viene chiamato l'opportuno metodo <strong>di</strong> notifica, queste liste sono esaminate<br />
ed i cambiamenti richiesti vengono attuati.<br />
Gli eventi considerati sono:<br />
1. Rimozione <strong>di</strong> un Behaviour<br />
2. Riattivazione <strong>di</strong> un Behaviour<br />
3. Reset <strong>di</strong> un Behaviour<br />
Queste situazioni sono amministrate al <strong>di</strong> fuori del Behaviour in quanto possono<br />
essere richieste, in qualche modo, <strong>di</strong>rettamente allo scheduler. Il caso del remove() è
Implementazione <strong>di</strong> riferimento - Scheduler 97<br />
banale: l'agente che vuole eliminare un proprio comportamento deve passare<br />
attraverso il proprio scheduler <strong>per</strong> ottenerlo (come <strong>per</strong> l'inserimento <strong>ad</strong> o<strong>per</strong>a <strong>di</strong><br />
<strong>ad</strong>d()). Il metodo remove(), quando si accorge che il Behaviour da eliminare è<br />
attualmente in esecuzione, invece <strong>di</strong> interrom<strong>per</strong>lo bruscamente, inserisce la<br />
richiesta in una lista (se non l'ha già fatto) e ritorna demandando a notifyExecuted()<br />
l'attuazione del comando.<br />
Le situazioni rimanenti acc<strong>ad</strong>ono in casi particolari.<br />
JADE richiede che, quando arriva un nuovo messaggio, l'Agent provveda al risveglio<br />
<strong>di</strong> tutti i suoi Behaviour blocked, in modo che questi possano eventualmente<br />
controllare se il messaggio è <strong>di</strong> loro <strong>per</strong>tinenza o meno, ed in questo caso bloccarsi<br />
nuovamente. Questo tipo <strong>di</strong> restart è effettuato passando attraverso la gerarchia<br />
degli scheduler (che si snoda passando <strong>per</strong> i CompositeBehaviour) e con<br />
l'invocazione, su ognuno <strong>di</strong> questi, del metodo restartAll(). La segnalazione, che<br />
potrebbe in apparenza essere inoltrata solo ai Behaviour blocked, riguarda in realtà<br />
anche i running: se un Behaviour running chiama il metodo block() durante l'action(),<br />
il suo stato viene cambiato in blocked solo al ritorno da questa ed un'eventuale<br />
restartAll() che avvenisse in tale con<strong>di</strong>zione andrebbe <strong>per</strong>so, lasciando il behaviour<br />
bloccato anche se un messaggio che potrebbe riguardarlo è giunto all'agente<br />
[Riqu<strong>ad</strong>ro 19].<br />
public synchronized void restartAll()<br />
{<br />
Collection subSchedulers=getSubSchedulers();<br />
Iterator i=subSchedulers.iterator();<br />
while(i.hasNext())<br />
{<br />
Scheduler s=(Scheduler)i.next();<br />
s.restartAll();<br />
}<br />
i=blockedBehaviours.iterator();<br />
while(i.hasNext())<br />
{<br />
Behaviour b=(Behaviour)i.next();<br />
b.restart();<br />
}<br />
restartedWhileRunningBehaviours=new LinkedList(runningBehaviours);<br />
}<br />
Riqu<strong>ad</strong>ro 19 : Scheduler.restartAll()
Implementazione <strong>di</strong> riferimento - Scheduler 98<br />
I problemi con il reset si verificano invece quando il metodo viene chiamato su un<br />
CompositeBehaviour. In tale situazione il Behaviour contenitore deve eseguire<br />
reset() su ogni sub-behaviour. Mentre <strong>per</strong> i casi precedenti l'o<strong>per</strong>azione poteva <strong>per</strong>ò<br />
essere asincrona, un reset() su un CompositeBehaviour non può <strong>di</strong>rsi effettuato<br />
finché non inoltrato su ogni Behaviour contenuto ed il mancato rispetto <strong>di</strong> tale vincolo<br />
può creare problemi d'inconsistenza. Se, <strong>ad</strong> esempio, un SequentialBehaviour riceve<br />
un reset(), questo deve riportare la sua lista nello stato iniziale e ricominciare con<br />
l'esecuzione del primo sub-behaviour; se il primo sub-behaviour era, <strong>per</strong>ò, già in<br />
esecuzione e non ha ricevuto il reset, tale Behaviour non ricomincia dallo stato<br />
iniziale, ma da uno stato già mo<strong>di</strong>ficato, inficiando il comando impartito al<br />
CompositeBehaviour.<br />
Per evitare queste situazioni il comando <strong>di</strong> reset <strong>di</strong> uno scheduler ha <strong>di</strong>versi<br />
accorgimenti:<br />
1. È dotato <strong>di</strong> una de<strong>ad</strong>line, ovvero <strong>di</strong> un termine massimo entro cui deve essere<br />
portato a termine<br />
2. Mo<strong>di</strong>fica lo stato dello scheduler in modo che durante la sua esecuzione non<br />
possano essere effettuate richieste <strong>di</strong> scheduling (schedule() ritorna null)
Implementazione <strong>di</strong> riferimento - Scheduler 99<br />
La de<strong>ad</strong>line è <strong>per</strong>entoria: se al suo sc<strong>ad</strong>ere i Behaviour che erano running non sono<br />
tutti ritornati (eseguendo quin<strong>di</strong> il reset()), lo scheduler ne forza il ritorno<br />
interrompendoli [Riqu<strong>ad</strong>ro 20].<br />
public synchronized void reset(long expiration)<br />
{<br />
De<strong>ad</strong>line.checkExpiration(expiration);<br />
resetting=true;<br />
Behaviour b;<br />
List re<strong>ad</strong>yAndBlocked=new LinkedList(re<strong>ad</strong>yBehaviours);<br />
re<strong>ad</strong>yAndBlocked.<strong>ad</strong>dAll(blockedBehaviours);<br />
Iterator i=re<strong>ad</strong>yAndBlocked.iterator();<br />
while(i.hasNext())<br />
{<br />
b=(Behaviour)i.next();<br />
b.reset();<br />
}<br />
terminatedBehavioursCounter=0;<br />
resettedWhileRunningBehaviours=new LinkedList(runningBehaviours);<br />
stopRunningBehaviours(expiration);<br />
lastExitValue=0;<br />
currentDone=false;<br />
currentExecuted=false;<br />
resetting=false;<br />
checkAndRequest();<br />
}<br />
Riqu<strong>ad</strong>ro 20 : Scheduler.reset()<br />
Questo tipo <strong>di</strong> forzatura causa <strong>di</strong>verse piccole mo<strong>di</strong>fiche <strong>di</strong> sincronizzazione al<br />
sistema, <strong>per</strong> tenere conto <strong>di</strong> situazioni particolari, ma il dettaglio raggiunto esula da<br />
questa trattazione e si consiglia la visione del co<strong>di</strong>ce <strong>per</strong> spiegazioni a riguardo.<br />
5.3.8 Sottoclassi Concrete<br />
L'intera amministrazione dei Behaviour è affidata ai meto<strong>di</strong> della classe base astratta.<br />
Le sue specificazioni concrete si occupano dell'implementazione dei meto<strong>di</strong><br />
concernenti la scelta dell'or<strong>di</strong>ne <strong>di</strong> schedulazione dei Behaviour loro affidati. I meto<strong>di</strong><br />
in questione sono choose(), check() ed hasTerminated().Oltre a questi, che devono<br />
essere implementati obbligatoriamente, alcune sottoclassi, estendono,<br />
sovrascrivendoli richiamando il metodo della su<strong>per</strong>classe ed aggiungendo co<strong>di</strong>ce
Implementazione <strong>di</strong> riferimento - Gestione delle interruzioni 100<br />
specifico, alcuni meto<strong>di</strong> già implementati in Schedule ed aggiungendone <strong>di</strong> nuovi <strong>per</strong><br />
scopi specifici. Le politiche dei sub-scheduler sono le stesse delle rispettive<br />
sottoclassi <strong>di</strong> CompositeBehaviour e le implementazioni sono generate sulla falsa<br />
riga <strong>di</strong> questi.<br />
Si ricorda che il metodo check() è la versione non mo<strong>di</strong>ficativa <strong>di</strong> choose(), che si<br />
occupa della selezione del Behaviour, mentre hasTerminated() racchiude la<br />
con<strong>di</strong>zione <strong>di</strong> terminazione dello scheduler (ed è quin<strong>di</strong> equivalente <strong>ad</strong><br />
un'interrogazione <strong>di</strong> done() sul CompositeBehaviour() corrispondente).<br />
Le sincronizzazioni presenti negli scheduler Sequential e FSM si basano sui valori<br />
delle variabili <strong>di</strong> stato currentDone e currentExecuted mo<strong>di</strong>ficate al ritorno delle<br />
sessioni d'esecuzione dal metodo Scheduler.notifyExecuted().<br />
5.4 Gestione delle interruzioni<br />
Oltre a situazioni normali, nelle quali il ciclo d'esecuzione <strong>di</strong> un Behaviour procede<br />
senza problemi passando dallo Scheduler, al Thre<strong>ad</strong>Dispatcher,<br />
all'EmbeddedThre<strong>ad</strong>, <strong>per</strong> poi ri<strong>per</strong>correre all'in<strong>di</strong>etro la catena, notificando la corretta<br />
terminazione della action() ed occupandosi del mantenimento del requisito <strong>di</strong><br />
coerenza dei moduli coinvolti, vi sono situazioni nelle quali un thre<strong>ad</strong> può subire,<br />
volontariamente o no, un'interruzione, evento che può acc<strong>ad</strong>ere anche mentre è in<br />
corso l'esecuzione del metodo action() <strong>di</strong> un Behaviour.<br />
Questa situazione eccezionale deve comunque essere gestita dal sistema in modo<br />
da non causare errori irrecu<strong>per</strong>abili che possano compromettere l'esecuzione <strong>di</strong> altri<br />
Behaviour o dell'intero Agent.<br />
5.4.1 Catena ascendente<br />
Come avviene <strong>per</strong> le notifiche <strong>di</strong> corretta terminazione, il sistema prevede una catena<br />
<strong>di</strong> notifiche anche nell'eventualità <strong>di</strong> un'interruzione <strong>di</strong> thre<strong>ad</strong>. Il meccanismo parte da<br />
EmbeddedThre<strong>ad</strong> e si propaga passando dal Thre<strong>ad</strong>Dispatcher <strong>per</strong> arrivare allo<br />
Scheduler competente.
Implementazione <strong>di</strong> riferimento - Gestione delle interruzioni 101<br />
Nell'eventualità prospettata, EmbeddedThre<strong>ad</strong>, che incapsula il thre<strong>ad</strong> d'esecuzione,<br />
chiama notifyInterrupted() sul Thre<strong>ad</strong>Dispatcher che lo amministra in modo che<br />
possa aggiornare i riferimenti contenuti nelle sue liste. Se l'interruzione avviene su un<br />
thre<strong>ad</strong> re<strong>ad</strong>y, basta eliminare il suo riferimento dalla lista dei re<strong>ad</strong>yBehaviours, ma<br />
quando il thre<strong>ad</strong> è running le cose si complicano.<br />
Dato che l'interruzione può avvenire sia mentre l'action() viene eseguita, sia mentre il<br />
thre<strong>ad</strong> è "sospeso" in attesa d'istruzioni, il Thre<strong>ad</strong>Dispatcher deve <strong>di</strong>scriminare tra i<br />
due casi. Quando un EmbeddedThre<strong>ad</strong> sospeso viene interrotto, questo ha già<br />
effettuato una richiesta <strong>per</strong> avere un nuovo Behaviour (chiamando<br />
requestBehaviour()) ed il Thre<strong>ad</strong>Dispatcher l'ha memorizzata: basta esaminare la<br />
coda delle richieste <strong>per</strong> scoprire se l'action() è stata interrotta o no [Riqu<strong>ad</strong>ro 21].<br />
public synchronized void notifyInterrupted(EmbeddedThre<strong>ad</strong> et)<br />
{<br />
if(re<strong>ad</strong>yThre<strong>ad</strong>s.contains(et)) re<strong>ad</strong>yThre<strong>ad</strong>s.remove(et);<br />
else if(runningThre<strong>ad</strong>s.remove(et))<br />
{<br />
if(requests.contains(et))<br />
{<br />
requests.remove(et);<br />
notify();<br />
}<br />
else<br />
{<br />
Behaviour b=et.getBehaviour();<br />
Scheduler s=(Scheduler) associationBS.remove(b);<br />
notify();<br />
s.notifyInterrupted(b);<br />
}<br />
}<br />
}<br />
Riqu<strong>ad</strong>ro 21 : Thre<strong>ad</strong>Dispatcher.notifyInterrupted()<br />
Nel caso l'esecuzione del Behaviour sia stata interrotta, il Thre<strong>ad</strong>Dispatcher<br />
provvede a passare la notifica allo scheduler interessato: da un mapping interno tra<br />
Behaviour e Scheduler, creato in fase <strong>di</strong> <strong>di</strong>spatching, viene recu<strong>per</strong>ato lo scheduler<br />
proprietario del Behaviour e su questo s'invoca Scheduler.notifyInterrupted() che
Implementazione <strong>di</strong> riferimento - Gestione delle interruzioni 102<br />
provvede a gestire la situazione [par. 5.3.6 Amministrazione dei cambiamenti <strong>di</strong> stato<br />
dei Behaviour].<br />
5.4.2 Catena <strong>di</strong>scendente<br />
L'amministrazione <strong>di</strong> queste notifiche è particolarmente delicata <strong>per</strong> il sistema, in<br />
quanto bi<strong>di</strong>rezionale: oltre alla catena ascendente sopra descritta, nella quale<br />
l'interruzione è considerata un evento indesiderato causato dall'esterno, vi è<br />
parallelamente una catena <strong>di</strong>scendente, nella quale sono gli stessi moduli del<br />
sistema a causare volontariamente l'interruzione dei Behaviour. Questa catena, nella<br />
quale ogni modulo a monte sfrutta le competenze <strong>di</strong> quello a valle, si verifica a<br />
seguito <strong>di</strong> o<strong>per</strong>azioni <strong>di</strong> terminazione delle attività, come <strong>ad</strong> esempio reset() e<br />
stopRunningBehaviour() <strong>di</strong> Scheduler [par. 5.3.7 Cambiamenti <strong>di</strong> stato imposti ai<br />
Behaviour dall'esterno], o stop() e stopRunningThre<strong>ad</strong>s() <strong>di</strong> Thre<strong>ad</strong>Dispatcher [par.<br />
5.2.3 Dispatching e gestione dei thre<strong>ad</strong>].<br />
Questi meto<strong>di</strong> possono ritornare solo una volta che hanno avuto la certezza che le<br />
interruzioni siano andate a buon fine: devono attendere l'arrivo delle notifiche. Così<br />
facendo, richieste e notifiche possono annidarsi. Questo comporta la creazione <strong>di</strong> un<br />
meccanismo <strong>di</strong> sincronizzazione sviluppato a livello <strong>di</strong> ogni modulo ma che<br />
garantisca anche la correttezza <strong>di</strong> tutta la catena: non si può sa<strong>per</strong>e anticipatamente<br />
da quale modulo parta la richiesta d'interruzione, se da Scheduler, da<br />
Thre<strong>ad</strong>Dispatcher o <strong>per</strong> cause esterne, ma il meccanismo deve funzionare<br />
ugualmente evitando sia problemi <strong>di</strong> de<strong>ad</strong>lock, sia mantenendo la coerenza <strong>di</strong> ogni<br />
modulo [Riqu<strong>ad</strong>ro 22].
Implementazione <strong>di</strong> riferimento - Gestione delle interruzioni 103<br />
private synchronized int interruptRunningThre<strong>ad</strong>s()<br />
{<br />
int notInterrupted=0;<br />
EmbeddedThre<strong>ad</strong> et;<br />
Iterator i=runningThre<strong>ad</strong>s.iterator();<br />
while(i.hasNext())<br />
{<br />
et=(EmbeddedThre<strong>ad</strong>)i.next();<br />
if(requests.contains(et))<br />
{<br />
requests.remove(et);<br />
runningThre<strong>ad</strong>s.remove(et);<br />
re<strong>ad</strong>yThre<strong>ad</strong>s.<strong>ad</strong>d(et);<br />
}<br />
else if(!et.interrupt()) notInterrupted++;<br />
}<br />
try<br />
{<br />
while(runningThre<strong>ad</strong>s.size()>notInterrupted) wait();<br />
}<br />
catch(InterruptedException ie) {}<br />
return notInterrupted;<br />
}<br />
Riqu<strong>ad</strong>ro 22 : Thre<strong>ad</strong>Dispatcher.interruptRunningThre<strong>ad</strong>s()<br />
Se Scheduler.stopRunningBehaviour() si affida <strong>per</strong> la sua esecuzione a<br />
Thre<strong>ad</strong>Dispatcher.stopRunningThre<strong>ad</strong>s(), quando quest'ultimo ritorna, lo stato <strong>di</strong><br />
Thre<strong>ad</strong>Dispatcher deve essere coerente (devono essere già arrivate tutte le notifiche<br />
d'interruzione) e le eventuali chiamate a Scheduler.notifyInterrupted() devono già<br />
essere state effettuate.<br />
All'interno dei meto<strong>di</strong> che richiedono l'interruzione [Riqu<strong>ad</strong>ro 22] si può, infatti, notare<br />
la presenza <strong>di</strong> chiamate a wait() sul monitor dell'oggetto; quando poi un metodo <strong>di</strong><br />
notifica d'interruzione viene invocato, nel corpo del co<strong>di</strong>ce, dopo che le o<strong>per</strong>azioni<br />
<strong>per</strong> rendere coerente lo stato sono state effettuate, si procede alla segnalazione con<br />
notify() [Riqu<strong>ad</strong>ro 21], e, solo dopo, all'inoltro della notifica al modulo a monte<br />
[Diagramma 36].
Implementazione <strong>di</strong> riferimento - Gestione delle interruzioni 104<br />
a : Agent<br />
stopRunningBehaviour(long)<br />
return from call<br />
s : Scheduler td : Thre<strong>ad</strong><br />
Dispatcher<br />
stopRunningThre<strong>ad</strong>s(long)<br />
wait() on monitor<br />
notifyInterrupted(b)<br />
remove b from running<br />
notify() on monitor<br />
interrupt( )<br />
wait() on monitor<br />
notifyInterrupted(et)<br />
remove et from running<br />
b:=getBehaviour( )<br />
s:=get b's scheduler<br />
notify() to monitor<br />
Diagramma 36 : UML Sequence Diagram <strong>per</strong> le catene <strong>di</strong> notifica delle interruzioni<br />
et :<br />
Embedded
Implementazione <strong>di</strong> riferimento - Gestione delle interruzioni 105<br />
5.4.3 Meto<strong>di</strong> non interrompibili<br />
Quando si effettuano chiamate a meto<strong>di</strong> che promettono <strong>di</strong> interrom<strong>per</strong>e ogni attività,<br />
bisogna considerare anche la possibilità che vi siano meto<strong>di</strong> non interrompibili<br />
[Riqu<strong>ad</strong>ro 23] e quin<strong>di</strong> evitare che chiamate a stopRunningThre<strong>ad</strong>s() e simili,<br />
attendendo una notifica che non avverrà mai, possano causare un blocco del<br />
sistema.<br />
class UninterruptableBehaviour extends Behaviour<br />
{<br />
public void action()<br />
{<br />
for(;;)<br />
{<br />
try<br />
{<br />
for(;;)<br />
{<br />
System.out.print('!');<br />
Thre<strong>ad</strong>.sleep(250);<br />
}<br />
}<br />
catch(InterruptedException ie)<br />
{}<br />
}<br />
}<br />
}<br />
Riqu<strong>ad</strong>ro 23 : Esempio <strong>di</strong> Behaviour non interrompibile
Implementazione <strong>di</strong> riferimento - Gestione delle interruzioni 106<br />
Per evitare tale inconveniente, il metodo interrupt() <strong>di</strong> EmbeddedThre<strong>ad</strong> è stato<br />
fornito <strong>di</strong> una de<strong>ad</strong>line: se l'interruzione avviene entro tale termine, il metodo ritorna<br />
true, altrimenti fallisce, ma ritorna ugualmente restituendo false [Riqu<strong>ad</strong>ro 24].<br />
public boolean interrupt()<br />
{<br />
synchronized(stateLock)<br />
{<br />
if(thre<strong>ad</strong>State!=CREATED && thre<strong>ad</strong>State!=TERMINATED &&<br />
thre<strong>ad</strong>State!=INTERRUPTED)<br />
{<br />
thre<strong>ad</strong>State=ABOUT_TO_INTERRUPT;<br />
theThre<strong>ad</strong>.interrupt();<br />
try<br />
{<br />
stateLock.wait(De<strong>ad</strong>line.DEFAULT);<br />
}<br />
catch(InterruptedException ie) {}<br />
}<br />
return (thre<strong>ad</strong>State==INTERRUPTED);<br />
}<br />
}<br />
Riqu<strong>ad</strong>ro 24 : EmbeddedThre<strong>ad</strong>.interrupt()<br />
Questo sistema <strong>di</strong> notifica della riuscita/fallimento dell'interruzione è sfruttato dai<br />
meto<strong>di</strong> a monte <strong>per</strong> evitare attese infinite: un contatore dei thre<strong>ad</strong> non interrompibili<br />
in<strong>di</strong>ca quante notifiche si devono effettivamente attendere.
Implementazione <strong>di</strong> riferimento - Gestione delle interruzioni 107<br />
5.4.4 Problemi con InterruptedException<br />
Thre<strong>ad</strong>Death<br />
Diagramma 37 : UML Class Diagram <strong>per</strong> la gerarchia delle Eccezioni <strong>di</strong> Java<br />
Java, come altri linguaggi <strong>di</strong> programmazione orientati agli oggetti, fornisce lo<br />
strumento delle eccezioni. Si tratta <strong>di</strong> un costrutto che <strong>per</strong>mette <strong>di</strong> gestire<br />
l'insorgenza <strong>di</strong> con<strong>di</strong>zioni particolari ed anomale <strong>di</strong> esecuzione, che possono<br />
verificarsi a run-time, in modo Object-Oriented: quando una situazione <strong>di</strong> errore viene<br />
rilevata è possibile interrom<strong>per</strong>e il normale flusso <strong>di</strong> controllo del co<strong>di</strong>ce e "lanciare"<br />
un oggetto che lo segnali; il flusso riprende nel punto <strong>di</strong> co<strong>di</strong>ce in cui oggetto in<br />
questione viene "catturato" e tale blocco <strong>di</strong> programma si occu<strong>per</strong>à del corretto<br />
recu<strong>per</strong>o del problema.<br />
Throwable<br />
Error Exception<br />
RuntimeException<br />
InterruptedException ArithmeticException<br />
La gerarchia delle eccezioni <strong>di</strong> Java prevede (oltre la classe base Throwable):
Implementazione <strong>di</strong> riferimento - Gestione delle interruzioni 108<br />
?? La classe Error: oggetti lanciati in con<strong>di</strong>zioni or<strong>di</strong>narie dallo stesso sistema<br />
run-time <strong>di</strong> Java che corrispondono a situazioni "catastrofiche" riguardanti il<br />
sistema stesso e che <strong>per</strong> questo non dovrebbero solitamente essere catturate<br />
(Es. OutOfMemoryError, Thre<strong>ad</strong>Death).<br />
?? La classe Exception: oggetti usati <strong>per</strong> segnalare con<strong>di</strong>zioni particolari che<br />
dovrebbero essere controllate e gestite dall'applicazione.<br />
?? La sottoclasse <strong>di</strong> Exception RuntimeException: oggetti <strong>per</strong> situazioni or<strong>di</strong>narie<br />
lanciate dallo stesso ambiente d'esecuzione (Es. ArithmeticException,<br />
NullPointerException).<br />
Mentre le eccezioni <strong>di</strong> tipo Error o RuntimeException possono essere lanciate<br />
all'interno <strong>di</strong> un metodo senza che questo debba <strong>di</strong>chiarare <strong>di</strong> farlo, <strong>per</strong> quanto<br />
riguarda la classe Exception e sottoclassi, queste devono essere obbligatoriamente<br />
catturate all'interno del metodo che le genera o, nel caso ciò non avvenga, ne deve<br />
essere data notifica nella <strong>di</strong>chiarazione del metodo stesso pena l'impossibilità <strong>di</strong><br />
compilazione: tali eccezioni vengono dette "controllate".
Implementazione <strong>di</strong> riferimento - Gestione delle interruzioni 109<br />
InterruptedException fa parte della categoria delle eccezioni controllate: se viene<br />
lanciata all'interno <strong>di</strong> un metodo e non catturata dal metodo stesso, deve essere<br />
<strong>di</strong>chiarata [Riqu<strong>ad</strong>ro 25].<br />
...<br />
//un metodo che lancia una InterruptedException o esegue un<br />
//metodo che <strong>di</strong>chiara <strong>di</strong> farlo, e non la cattura internamente<br />
//deve <strong>di</strong>chiararla nella clausula throws<br />
public void throwsInterrupted() throws InterruptedException<br />
{<br />
doesntThrowInterruptedButDeclares();<br />
throw new InterruptedException();<br />
}<br />
//un metodo che <strong>di</strong>chiara <strong>di</strong> lanciare una InterruptedException<br />
//non è obbligato a lanciarla veramente<br />
public void doesntThrowInterruptedButDeclares()<br />
throws InterruptedException<br />
{}<br />
...<br />
//Un metodo che al suo interno cattura una InterruptedException<br />
//deve avere nel corpo del try una chiamata che <strong>di</strong>chiara <strong>di</strong><br />
//lanciarla<br />
public void catchInterrupted()<br />
{<br />
try<br />
{<br />
//se commento il blocco sottostante non posso compilare<br />
{<br />
throwsInterrupted();<br />
doesntThrowInterruptedButDeclares();<br />
}<br />
doesntThrowInterrupted();<br />
}<br />
catch(InterruptedException ie) {}<br />
}<br />
...<br />
Riqu<strong>ad</strong>ro 25 : TestInterruptedException: prove <strong>per</strong> InterruptedException<br />
Il problema nasce <strong>per</strong> la particolare <strong>di</strong>chiarazione del metodo action() <strong>di</strong> Behaviour<br />
che non <strong>di</strong>chiara <strong>di</strong> poter lanciare tale eccezione. In con<strong>di</strong>zioni normali, infatti, una<br />
action() è considerata non interrompibile e quin<strong>di</strong>, una volta entrati nel suo corpo<br />
d'esecuzione, si dovrebbe aspettare semplicemente che termini: è compito del<br />
programmatore fare in modo che all'interno della chiamata non si possano verificare
Implementazione <strong>di</strong> riferimento - Gestione delle interruzioni 110<br />
situazioni quali lunghi blocchi in attesa <strong>di</strong> eventi esterni, cicli infiniti o situazioni <strong>di</strong><br />
de<strong>ad</strong>lock che congelerebbero l'intero sistema.<br />
In alcuni casi è <strong>per</strong>ò necessario tenere conto <strong>di</strong> un'eventuale interruzione<br />
dall'esterno. Si supponga <strong>ad</strong> esempio che l'action() debba interrogare uno stream <strong>di</strong><br />
dati: dovrà tentare una re<strong>ad</strong>() e questa, essendo bloccante, potrebbe causare<br />
situazioni <strong>di</strong> stallo tipiche del "blocco critico" se mal gestita; si deve <strong>per</strong>tanto<br />
considerare la possibilità <strong>di</strong> dover interrom<strong>per</strong>e l'attesa e <strong>di</strong> conseguenza la action().<br />
In presenza <strong>di</strong> eventi "eccezionali" come quello descritto, una buona tecnica <strong>di</strong><br />
programmazione dovrebbe prevedere:<br />
1. Cattura dell'eccezione lanciata.<br />
2. Recu<strong>per</strong>o dello stato interno a quello coerente più vicino temporalmente.<br />
3. Nuova segnalazione, alle classi che gestiscono l'oggetto, dell'eccezione avvenuta<br />
in modo che possano recu<strong>per</strong>are anch'essi la loro coerenza.<br />
L'ultimo passo richiede che action() <strong>di</strong>chiari nella sua clausola throws <strong>di</strong> poter<br />
lanciare InterruptedException, ma questo non avviene. Se manca il passo 3 può<br />
verificarsi che lo stato del Behaviour venga recu<strong>per</strong>ato, ma rimanga invalidato quello<br />
del sistema che lo comprende (EmbeddedThre<strong>ad</strong>, Thre<strong>ad</strong>Dispatcher e Scheduler).
Implementazione <strong>di</strong> riferimento - Gestione delle interruzioni 111<br />
La classe EmbeddedThre<strong>ad</strong>, infatti, cattura e gestisce l'eccezione <strong>di</strong> interruzione del<br />
thre<strong>ad</strong> ed a sua volta notifica l'evento al Thre<strong>ad</strong>Dispatcher (<strong>per</strong> l'eliminazione dalle<br />
liste in esso contenute del thre<strong>ad</strong> interrotto). Questo, ripristinata la propria coerenza,<br />
provvede a rilanciare la notifica allo Scheduler competente <strong>per</strong>ché possa<br />
amministrare la terminazione del Behaviour [Riqu<strong>ad</strong>ro 26].<br />
...<br />
catch(InterruptedException ie)<br />
{<br />
synchronized(stateLock)<br />
{<br />
thre<strong>ad</strong>State=INTERRUPTED;<br />
stateLock.notify();<br />
}<br />
theThre<strong>ad</strong>Dispatcher.notifyInterrupted(this);<br />
}<br />
...<br />
Riqu<strong>ad</strong>ro 26 : EmbeddedThre<strong>ad</strong>: inizio della catena <strong>di</strong> notifiche dell'interruzione del thre<strong>ad</strong> in<br />
run().<br />
Problema duale, legato al fatto che InterruptedException sia controllata, è che un<br />
metodo che cattura un'eccezione controllata deve avere, all'interno del "blocco try":<br />
?? Un'esplicita clausola throw che lanci tale eccezione o<br />
?? Almeno un metodo che <strong>di</strong>chiari <strong>di</strong> lanciarla [Riqu<strong>ad</strong>ro 25].
Implementazione <strong>di</strong> riferimento - Classi <strong>di</strong> utilità 112<br />
Per risolvere momentaneamente questo specifico problema, si è introdotto, dove<br />
necessario, un wrap<strong>per</strong> <strong>per</strong> la chiamata <strong>ad</strong> action() in modo che il metodo <strong>di</strong>chiari <strong>di</strong><br />
lanciare InterruptedException [Riqu<strong>ad</strong>ro 27].<br />
...<br />
try<br />
{<br />
theBehaviour=theScheduler.schedule();<br />
actionWrap<strong>per</strong>(theBehaviour);<br />
theScheduler.notifyExecuted(theBehaviour);<br />
}<br />
catch(InterruptedException ie)<br />
{<br />
theScheduler.notifyInterrupted(theBehaviour);<br />
}<br />
...<br />
private void actionWrap<strong>per</strong>(Behaviour b) throws InterruptedException<br />
{<br />
b.action();<br />
}<br />
...<br />
Riqu<strong>ad</strong>ro 27 : Thre<strong>ad</strong>Dispatcher: su<strong>per</strong>amento dei problemi con InterruptedException all'interno<br />
<strong>di</strong> <strong>di</strong>spatch().<br />
Soluzione più elegante, <strong>per</strong> salvare sia la <strong>di</strong>chiarazione <strong>di</strong> action(), sia la creazione<br />
della catena <strong>di</strong> notifiche, sarebbe quella <strong>di</strong> creare una sottoclasse <strong>di</strong> Error<br />
specificatamente con lo scopo <strong>di</strong> segnalare interruzioni della action() stessa: Error e<br />
classi derivate non hanno l'obbligo <strong>di</strong> essere <strong>di</strong>chiarate e possono comunque essere<br />
catturate. Questo genere <strong>di</strong> miglioria comporterebbe alcune piccole mo<strong>di</strong>fiche nei soli<br />
punti d'ingresso delle catene <strong>di</strong> notifica, l<strong>ad</strong>dove invece <strong>di</strong> catturare<br />
InterruptedException lanciata da action() si gestirebbe il nuovo errore.<br />
5.5 Classi <strong>di</strong> utilità<br />
Alcuni costrutti, <strong>ad</strong>o<strong>per</strong>ati in maniera non proprietaria da più <strong>di</strong> una classe, sono stati<br />
separati e resi <strong>di</strong>sponibili <strong>per</strong> l'utilità comune dei <strong>di</strong>versi attori. Si tratta <strong>di</strong> classi che
Implementazione <strong>di</strong> riferimento - Classi <strong>di</strong> utilità 113<br />
non corrispondono <strong>ad</strong> entità interagenti del sistema, ma forniscono strumenti da<br />
queste ampiamente <strong>ad</strong>o<strong>per</strong>ati.<br />
5.5.1 AssociationTimerObject<br />
Diagramma 38 : UML Class Diagram <strong>di</strong> AssociationTimerObject<br />
La classe AssociationTimerObject nasce dall'esigenza, all'interno <strong>di</strong> quelle classi che<br />
devono eseguire azioni temporizzate implementando l'interfaccia TimerListener<br />
(Scheduler e Thre<strong>ad</strong>Dispatcher::Re<strong>ad</strong>yThre<strong>ad</strong>sManager), <strong>di</strong> mantenere una<br />
mappatura tra i Timer degli eventi innescati ed i riferimenti agli oggetti su cui<br />
compiere tali azioni.<br />
AssociationTimerObject<br />
+ <strong>ad</strong>dPair(o : Object, t : Timer)<br />
+ getPeer(o : Object) : Object<br />
+ getNextToExpire() : Object<br />
+ removeMapping(o : Object) : Object<br />
+ size() : int<br />
+ contains(o : Object) : boolean<br />
+ getTimersIterator() : Iterator
Implementazione <strong>di</strong> riferimento - Classi <strong>di</strong> utilità 114<br />
Il meccanismo alla base <strong>di</strong> tali temporizzazioni può così riassumersi [Riqu<strong>ad</strong>ro 28]:<br />
1. Creazione <strong>di</strong> un Timer e suo innesco tramite il TimerDispatcher<br />
2. Creazione dell'associazione tra Timer ed istanza soggetta all'o<strong>per</strong>azione<br />
temporizzata<br />
3. Alla sc<strong>ad</strong>enza del Timer, esecuzione <strong>di</strong> doTimeout() (a cui viene passato il Timer<br />
invocante)<br />
4. Recu<strong>per</strong>o dell'oggetto tramite il riferimento al suo Timer<br />
5. Esecuzione dell'o<strong>per</strong>azione.<br />
...<br />
private AssociationTimerObject pen<strong>di</strong>ngTimers=<br />
new AssociationTimerObject();<br />
...<br />
public synchronized void restartLater(Behaviour b, long millis)<br />
{<br />
if(b==null) throw new IllegalArgumentException();<br />
if(millis
Implementazione <strong>di</strong> riferimento - Classi <strong>di</strong> utilità 115<br />
memorizzazione dei Timer: oltre a recu<strong>per</strong>are una mappatura conoscendo il<br />
"compagno" dell'oggetto da cercare, è possibile ottenere <strong>di</strong>rettamente l'oggetto con il<br />
Timer più prossimo a sc<strong>ad</strong>ere (chiamando il metodo getNextToExpire()).<br />
I limiti imposti alla classe sono:<br />
1. Non è consentito l'inserimento <strong>di</strong> oggetti "null"<br />
2. Non è possibile associare un oggetto <strong>di</strong> tipo Timer <strong>ad</strong> un Timer<br />
3. Non sono ammessi elementi duplicati: può esistere una sola mappatura tra un<br />
Timer ed un Object (e viceversa)<br />
Per quanto riguarda le o<strong>per</strong>azioni <strong>di</strong> recu<strong>per</strong>o, il metodo getPeer() accetta come<br />
argomento un oggetto generico provvedendo a <strong>di</strong>scernere i casi in cui questo sia<br />
istanza <strong>di</strong> Timer dagli altri ed o<strong>per</strong>ando appropriatamente il recu<strong>per</strong>o del compagno.<br />
Nel caso in cui al metodo sia passato un oggetto <strong>di</strong> tipo Timer, il confronto con le<br />
chiavi contenute all'interno viene effettuato sfruttando equals() e compareTo(): due<br />
istanze <strong>di</strong>verse <strong>di</strong> Timer con identico contenuto recu<strong>per</strong>ano lo stesso oggetto<br />
[Riqu<strong>ad</strong>ro 29].<br />
public Object getPeer(Object o)<br />
{<br />
Object theObject=null;<br />
if(o!=null)<br />
if(o instanceof Timer)<br />
{<br />
Timer theTimer=(Timer)o,t=null;<br />
Set timersSet=timerToObject.keySet();<br />
Iterator i=timersSet.iterator();<br />
while(i.hasNext())<br />
{<br />
t=(Timer)i.next();<br />
if(t.equals(theTimer)) break;<br />
}<br />
if(t.equals(theTimer))<br />
theObject=timerToObject.get(t);<br />
}<br />
else<br />
theObject=objectToTimer.get(o);<br />
return theObject;<br />
}<br />
Riqu<strong>ad</strong>ro 29 : AssociationTimerObject.getPeer()
Implementazione <strong>di</strong> riferimento - Classi <strong>di</strong> utilità 116<br />
5.5.2 De<strong>ad</strong>line<br />
Diagramma 39 : UML Class Diagram <strong>di</strong> De<strong>ad</strong>line<br />
La classe De<strong>ad</strong>line è una piccola classe d'utilità il cui scopo è quello <strong>di</strong><br />
standar<strong>di</strong>zzare ed importare una serie <strong>di</strong> costanti da utilizzare in associazione ai<br />
meto<strong>di</strong> temporizzati. Definisce inoltre un metodo statico checkExpiration() con lo<br />
scopo <strong>di</strong> controllare che i valori passati come parametri abbiano un significato fisico e<br />
siano compresi nel range <strong>di</strong> vali<strong>di</strong>tà del parametro. I nomi <strong>di</strong> meto<strong>di</strong> e variabili sono<br />
autoesplicativi: i valori del parametro vanno da NOW, che implica una de<strong>ad</strong>line a<br />
sc<strong>ad</strong>enza imme<strong>di</strong>ata, a NEVER <strong>per</strong> in<strong>di</strong>care una sc<strong>ad</strong>enza teoricamente posta<br />
all'infinito.<br />
Il comportamento delle chiamate temporizzate varia a seconda del metodo stesso,<br />
ma in generale può così riassumersi: il metodo cerca <strong>di</strong> portare a termine<br />
l'o<strong>per</strong>azione entro la de<strong>ad</strong>line temporale; se così è ritorna, altrimenti può:<br />
1. Forzare l'o<strong>per</strong>azione e la terminazione della chiamata e ritornare (come in stop()<br />
<strong>di</strong> Thre<strong>ad</strong>Dispatcher) [par. 5.2.3 Dispatching e gestione dei thre<strong>ad</strong>]<br />
2. Riportare un fallimento (come in suspend() <strong>di</strong> EmbeddedThre<strong>ad</strong>) [par. 5.1.4<br />
Transizioni temporizzate]<br />
De<strong>ad</strong>line<br />
+$ NOW : long = 0<br />
+$ NEVER : long = -1<br />
+$ DEFAULT : long = 5000<br />
+ De<strong>ad</strong>line(millis : long = DEFAULT)<br />
+ setExpiration(millis : long)<br />
+ getExpiration() : long<br />
+ checkExpiration(expiration : long)<br />
La classe può anche essere istanziata nei casi in cui servisse una de<strong>ad</strong>line variabile<br />
e settabile <strong>per</strong> una data istanza [par. 5.2.5 Re<strong>ad</strong>yThre<strong>ad</strong>sManager].
Implementazione <strong>di</strong> riferimento - Mo<strong>di</strong>fiche all'ambiente 117<br />
5.6 Mo<strong>di</strong>fiche all'ambiente<br />
Le classi descritte finora sono state create dal nulla e non facevano<br />
precedentemente parte del framework. L'integrazione dei nuovi moduli con il core <strong>di</strong><br />
JADE ha <strong>per</strong>ò richiesto <strong>di</strong>verse mo<strong>di</strong>fiche <strong>per</strong> quelle classi <strong>di</strong>rettamente interessate<br />
con l'amministrazione dei Behaviour. Le mo<strong>di</strong>fiche necessarie, non tutte<br />
effettivamente realizzate, sono descritte in questo paragrafo e sono state fornite<br />
come estensioni delle classi chiamate in causa in modo da evitare mo<strong>di</strong>fiche <strong>di</strong>rette<br />
all'ambiente. Una volta che il modello sarà sufficientemente testato e si saranno<br />
risolti problemi <strong>di</strong> compatibilità con le applicazioni sviluppate precedentemente, si<br />
potrà procedere con l'effettiva integrazione.<br />
5.6.1 Behaviour<br />
Il passaggio <strong>di</strong> responsabilità avvenuto tra Agent e le nuove classi implementate,<br />
unitamente all'<strong>ad</strong>ozione del modello multi-thre<strong>ad</strong>ed, hanno comportato alcune<br />
mo<strong>di</strong>fiche alla classe Behaviour che vengono qui illustrate.<br />
Essendo ora lo Scheduler <strong>ad</strong> occuparsi della gestione dei block() e restart(), ogni<br />
Behaviour, come prima aveva bisogno <strong>di</strong> un riferimento all'Agent proprietario, ora<br />
necessita <strong>di</strong> un riferimento allo Scheduler che lo amministra <strong>per</strong> potervi invocare<br />
restartLater() e notifyRestarted(): si è introdotto il metodo setScheduler() e si sono<br />
mo<strong>di</strong>ficati block(millis) e restart().<br />
Anche la gestione <strong>di</strong> onStart() ha subito un passaggio <strong>di</strong> managing: mentre prima era<br />
il Behaviour stesso <strong>ad</strong> occuparsene tramite chiamate <strong>ad</strong> actionWrap<strong>per</strong>() invece che<br />
action(), <strong>ad</strong>esso anche questa gestione è passata allo scheduler. Il metodo<br />
actionWrap<strong>per</strong>() può ora essere eliminato, mentre ne è stato aggiunto uno nuovo <strong>per</strong><br />
l'ispezione dello startFlag, passo necessario <strong>per</strong> conoscere se il Behaviour è alla sua<br />
prima esecuzione: tale metodo è yetStarted(). Anche onStart() ha subito una piccola<br />
mo<strong>di</strong>fica, in quanto ora è lui stesso che, dopo la sua esecuzione, cambia startFlag da<br />
true a false: estensioni <strong>di</strong> onStart() in sottoclassi dovrebbero quin<strong>di</strong> ricordarsi <strong>di</strong><br />
chiamare su<strong>per</strong>.onStart().
Implementazione <strong>di</strong> riferimento - Mo<strong>di</strong>fiche all'ambiente 118<br />
Più delicati sono i problemi <strong>di</strong> sincronizzazione degli accessi dovuti al nuovo modello<br />
multi-thre<strong>ad</strong>ed: avendo a <strong>di</strong>sposizione più linee d'esecuzione bisogna controllare che<br />
le chiamate ai meto<strong>di</strong> <strong>di</strong> Behaviour ed Agent (che possono essere invocati anche<br />
all'interno delle action()) siano eseguite sequenzialmente. La questione, non risolta<br />
nell'ambito del presente lavoro, risulta problematica in quanto non è possibile<br />
mo<strong>di</strong>ficare semplicemente le definizioni dei meto<strong>di</strong> <strong>di</strong> Behaviour, poiché questa<br />
azione comporterebbe la mo<strong>di</strong>fica <strong>di</strong> parte del co<strong>di</strong>ce finora sviluppato da tutti gli<br />
utenti <strong>di</strong> JADE: è quin<strong>di</strong> necessario trovare altri sistemi (<strong>ad</strong> esempio lock interni ai<br />
meto<strong>di</strong>).<br />
5.6.2 CompositeBehaviour<br />
Diagramma 40 : UML Class Diagram <strong>di</strong> CompositeBehaviour e componenti<br />
La sottoclasse <strong>di</strong> Behaviour, CompositeBehaviour, avendo il modello implementato<br />
separato quelle funzioni <strong>di</strong> scheduling che erano in essa incorporate, risulta<br />
particolarmente mo<strong>di</strong>ficata.<br />
1<br />
CompositeBehaviour<br />
1<br />
Scheduler Thre<strong>ad</strong>Dispatcher<br />
Il nuovo modello riduce le sue responsabilità a semplice contenitore <strong>di</strong> sub-<br />
behaviour, affidando allo scheduler, che deve incapsulare, la totale gestione <strong>di</strong><br />
questi. Il CompositeBehaviour può, inoltre, essere dotato <strong>di</strong> un Thre<strong>ad</strong>Dispatcher<br />
1<br />
0..1
Implementazione <strong>di</strong> riferimento - Mo<strong>di</strong>fiche all'ambiente 119<br />
<strong>per</strong>sonale <strong>per</strong> implementare la struttura gerarchica <strong>di</strong> cui nella descrizione del<br />
modello [cap. 4 Modello <strong>di</strong> Concorrenza proposto].<br />
Meto<strong>di</strong> come scheduleFirst(), scheduleNext(), checkTermination() e getCurrent()<br />
risultano ora su<strong>per</strong>flui: i primi <strong>per</strong>ché <strong>di</strong>venuti <strong>di</strong> competenza dello Scheduler,<br />
getCurrent() poiché un modello, in generale, multi-thre<strong>ad</strong>ed non può sostenere il<br />
concetto <strong>di</strong> "riferimento al Behaviour attualmente in esecuzione", in quanto ve ne<br />
possono essere simultaneamente più <strong>di</strong> uno. Altri meto<strong>di</strong>, come done(), reset() e<br />
restartAllSubBehaviours() richiamano gli equivalenti presenti in Scheduler.<br />
Il metodo action(), che gestiva praticamente lo scheduling dei sub-behaviour, ora non<br />
fa altro che invocare <strong>di</strong>spatch() sul riferimento che ha al Thre<strong>ad</strong>Dispatcher: nel caso<br />
che il CompositeBehaviour non abbia un Thre<strong>ad</strong>Dispatcher proprio, <strong>di</strong>spatch() viene<br />
invocato su quello del "p<strong>ad</strong>re".<br />
Sono stati aggiunti, infine, nuovi meto<strong>di</strong>, alcuni dei quali nascosti all'utente, <strong>per</strong><br />
o<strong>per</strong>azioni quali la notifica dei blocchi (o risvegli) a causa <strong>di</strong> mo<strong>di</strong>fiche nel set <strong>di</strong> sub-<br />
behaviour [par. 5.3.6 Amministrazione dei cambiamenti <strong>di</strong> stato dei Behaviour] e<br />
l'amministrazione del Thre<strong>ad</strong>Dispatcher <strong>per</strong>sonale.<br />
Per fornire un CompositeBehaviour <strong>di</strong> un Thre<strong>ad</strong>Dispatcher, ed evitare che l'utente<br />
debba manipolare riferimenti a Thre<strong>ad</strong>Dispatcher esterni alla classe, si è creato il<br />
metodo provideWithAThre<strong>ad</strong>Dispatcher() che effettua la creazione ed inizializzazione<br />
del Thre<strong>ad</strong>Dispatcher <strong>di</strong>rettamente all'interno <strong>di</strong> CompositeBehaviour: questo<br />
meccanismo garantisce così maggiore sicurezza e controllo sul rispetto dei vincoli<br />
del modello. Per le stesse ragioni, sono stati creati i meto<strong>di</strong><br />
hasItsOwnThre<strong>ad</strong>Dispatcher(), che ritorna true nel caso un CompositeBehaviour<br />
abbia un Thre<strong>ad</strong>Dispatcher <strong>di</strong> proprietà, getTheThre<strong>ad</strong>Dispatcher() e<br />
removeTheThre<strong>ad</strong>Dispatcher(). Accanto a questi meto<strong>di</strong> pubblici, sono stati forniti<br />
(non solo all'interno <strong>di</strong> CompositeBehaviour) meto<strong>di</strong> <strong>per</strong> la navigazione della<br />
gerarchia <strong>di</strong> Behaviour e relativi Scheduler e Thre<strong>ad</strong>Dispatcher <strong>ad</strong> uso interno<br />
(propagazione <strong>di</strong> coman<strong>di</strong>, interrogazione sullo stato <strong>di</strong> attività dei thre<strong>ad</strong> <strong>di</strong> un Agent,<br />
etc.).
Implementazione <strong>di</strong> riferimento - Mo<strong>di</strong>fiche all'ambiente 120<br />
5.6.3 Agent<br />
Diagramma 41 : UML Class Diagram <strong>di</strong> Agent e componenti<br />
Il nuovo modello comporta <strong>di</strong>verse mo<strong>di</strong>fiche anche <strong>per</strong> la classe Agent. Queste<br />
sono qui solamente descritte e necessitano ancora della realizzazione.<br />
Agent, innanzi tutto, deve contenere uno Scheduler ed un Thre<strong>ad</strong>Dispatcher<br />
top-level. Costruttori appositi forniti con dette classi consentono un'inizializzazione<br />
mirata <strong>per</strong> le istanze a livello dell'Agent, passando <strong>di</strong>rettamente riferimenti<br />
all'ambiente run-time (in particolare al TimerDispatcher) ed all'Agent contenitore.<br />
Dovendo, sia il top-Scheduler, sia il top-Thre<strong>ad</strong>Dispatcher, possedere riferimenti<br />
reciproci, la sequenza <strong>di</strong> creazione deve essere la seguente: prima viene creato lo<br />
Scheduler, passando il riferimento al TimerDispatcher, poi il Thre<strong>ad</strong>Dispatcher, che<br />
ottiene puntatori <strong>per</strong> TimerDispatcher, Agent e lo Scheduler appena creato.<br />
All'interno del costruttore del Thre<strong>ad</strong>Dispatcher si provvede, poi, <strong>ad</strong> assegnare un<br />
suo riferimento al top-Scheduler, chiudendo la catena.<br />
1<br />
Ora che Agent possiede riferimenti a scheduler e <strong>di</strong>spatcher, può servirsene <strong>per</strong><br />
l'amministrazione dei Behaviour.<br />
1<br />
Agent<br />
Scheduler Thre<strong>ad</strong>Dispatcher<br />
Meto<strong>di</strong> come restartLater(), notifyRestarted() e doTimeout() <strong>di</strong>ventano inutili, in<br />
quanto tali o<strong>per</strong>azioni riguardano ora lo scheduler; così come la classe incapsulata<br />
AssociationTB, sostituita da AssociationTimerObject, e la sua istanza pen<strong>di</strong>ngTimers,<br />
1<br />
1
Implementazione <strong>di</strong> riferimento - Testing dell'implementazione 121<br />
anch'essa <strong>di</strong> competenza del modulo <strong>di</strong> scheduling; i meto<strong>di</strong> <strong>ad</strong>dBehaviour() e<br />
removeBehaviour() vanno invece riscritti utilizzando i corrispettivi del top-Scheduler.<br />
Quando Agent si trova nel suo stato ACTIVE, invece <strong>di</strong> effettuare lo scheduling ed<br />
amministrare <strong>di</strong>rettamente i Behaviour (con i controlli <strong>per</strong> i risvegli, esecuzioni <strong>di</strong><br />
onEnd(), etc.), provvede semplicemente <strong>ad</strong> invocare <strong>di</strong>spatch() sul suo<br />
Thre<strong>ad</strong>Dispatcher, ed il sistema si occu<strong>per</strong>à del resto: portare in esecuzione un<br />
Behaviour su un thre<strong>ad</strong>.<br />
Anche i meto<strong>di</strong> che si occupano <strong>di</strong> gestire il life-cycle dell'Agent hanno bisogno <strong>di</strong><br />
mo<strong>di</strong>fiche. Come prima cosa devono essere sincronizzati a causa del possibile<br />
accesso simultaneo da parte <strong>di</strong> Behaviour in esecuzione su thre<strong>ad</strong> <strong>di</strong>versi. Quei<br />
meto<strong>di</strong> che forniscono il supporto <strong>per</strong> la mobilità, che interrompendo il thre<strong>ad</strong><br />
dell'agente ne fermavano ogni attività, devono considerare il fatto che in un sistema<br />
multi-thre<strong>ad</strong>ed questo non è più vero, e devono ricorrere agli appositi meto<strong>di</strong> forniti<br />
dal Thre<strong>ad</strong>Dispatcher [par. 5.2.3 Dispatching e gestione dei thre<strong>ad</strong>] (questo vale<br />
anche <strong>per</strong> doDelete()).<br />
Una precisazione va infine fatta sul tipo <strong>di</strong> scheduler da utilizzare come top-level:<br />
questo deve terminare solo insieme all'agente (un top-scheduler, anche se non ha<br />
Behaviour re<strong>ad</strong>y, deve avere un metodo hasTerminated() che ritorna sempre false).<br />
Per questo particolare scopo si è pensato alla classe ParallelScheduler con un<br />
apposito valore <strong>per</strong> il parametro "endCon<strong>di</strong>tion" pari a NEVER.<br />
5.7 Testing dell'implementazione<br />
Parallelamente allo sviluppo dell'implementazione si è provveduto anche al suo<br />
testing <strong>per</strong> verificarne la correttezza nelle situazioni d'uso più comuni. Questa fase ha<br />
portato alla creazione <strong>di</strong> una piccola "suite" <strong>di</strong> verifica, che completa il lavoro. Questo<br />
paragrafo si occupa <strong>di</strong> descrivere brevemente tale suite nella sua architettura.<br />
Tutti i sorgenti fanno parte del package Testing, e sono contenuti nell'apposita<br />
<strong>di</strong>rectory. Il modulo <strong>per</strong> l'esecuzione è la classe MainTest: all'interno del suo metodo<br />
main() sono effettuate le chiamate ai meto<strong>di</strong> statici test() delle <strong>di</strong>verse classi <strong>di</strong>
Implementazione <strong>di</strong> riferimento - Testing dell'implementazione 122<br />
testing. Agendo sul suo co<strong>di</strong>ce si possono commentare i test che non ci interessano,<br />
selezionandone solo alcuni.<br />
Le classi <strong>di</strong> testing sono chiamate anteponendo "Test" al nome della classe da<br />
testare od al tipo <strong>di</strong> o<strong>per</strong>azioni verificate ("TestEmbeddedThre<strong>ad</strong>", "TestRaces").<br />
Quando si esegue un test su una classe incapsulata dentro un'altra il nome della<br />
classe contenitrice precede quello della sottoclasse ("TestEmbeddedThre<strong>ad</strong>Mutex").<br />
Le classi da testare sono state leggermente mo<strong>di</strong>ficate <strong>per</strong> consentire le simulazioni<br />
necessarie e l'interfacciamento con l'ambiente simulato.<br />
Per avere un ambiente fittizio attorno alle classi da testare senza mo<strong>di</strong>ficarne troppo<br />
il co<strong>di</strong>ce ed il tipo <strong>di</strong> interazioni, si sono create delle interfacce con lo stesso nome e<br />
meto<strong>di</strong> pubblici della classe da simulare ed implementazioni vuote che alla chiamata<br />
<strong>di</strong> un metodo visualizzassero semplicemente un messaggio sullo schermo. Ogni<br />
interfaccia e relativa implementazione vuota, chiamata con lo stesso nome seguito<br />
da "EmptyImpl", sono state inserite in un file con il nome della classe (es.<br />
"Scheduler.java" contiene l'interfaccia "Scheduler" e l'implementazione<br />
"SchedulerEmptyImpl").<br />
Quando una classe fittizia doveva fare più che semplicemente visualizzare un<br />
messaggio, all'interno della classe <strong>di</strong> testing si è provveduto <strong>ad</strong> estendere<br />
l'implementazione vuota <strong>per</strong> gli scopi specifici: tali classi, <strong>per</strong> <strong>di</strong>fferenziarle l'una<br />
dall'altra, sono state chiamate usando le iniziali delle parole che compongono il nome<br />
della classe <strong>di</strong> testing (es. l'estensione <strong>di</strong> "SchedulerEmptyImpl" in<br />
"TestThre<strong>ad</strong>DispatcherRequestsManager" ha nome "SchedulerTTDRMImpl").<br />
Le reali implementazioni dei moduli sviluppati sono invece state chiamate<br />
aggiungendo "Impl" al nome reale; a questa regola vi sono alcune eccezioni <strong>per</strong><br />
quelle classi che non devono apparire nell'ambiente simulato e che hanno quin<strong>di</strong><br />
mantenuto il loro vero nome.<br />
Oltre alle classi facenti parte del modello, ve ne sono altre create appositamente <strong>per</strong><br />
il testing: Stars che rappresenta un semplice Behaviour <strong>di</strong> prova e Timer che<br />
implementa una versione semplificata dell'omonima classe del package j<strong>ad</strong>e.core.
Conclusioni 123<br />
6 Conclusioni<br />
Il modello d'esecuzione Thre<strong>ad</strong>-<strong>per</strong>-Agent utilizzato da JADE, pur essendo efficiente,<br />
mostra i suoi limiti quando accoppiato con il modello comportamentale dei behaviour.<br />
In tale para<strong>di</strong>gma, nell'ambito delle abilità <strong>di</strong> composizione del modello, si<br />
enfatizzano strumenti che <strong>di</strong>stinguono tra esecuzioni sequenziali <strong>di</strong> sub-behaviour ed<br />
aggregati gerarchicamente paralleli. In realtà tale concorrenza è solo virtuale in<br />
quanto implementata da un sistema single-thre<strong>ad</strong>ed attraverso uno scheduling<br />
round-robin delle sotto-attività raggruppate.<br />
L'inserimento, all'interno d'ogni singolo Agent, <strong>di</strong> un vero sistema multi-thre<strong>ad</strong>ed<br />
consente <strong>ad</strong> un ParallelBehaviour <strong>di</strong> essere realmente tale, e porta tutti i vantaggi <strong>ad</strong><br />
esso legati: un Behaviour bloccato non ritarderà più necessariamente l'esecuzione <strong>di</strong><br />
tutti gli altri, dando all'agente la possibilità <strong>di</strong> ottimizzare protocolli <strong>di</strong> comunicazione e<br />
re<strong>per</strong>imento <strong>di</strong> dati da fonti <strong>di</strong>verse.<br />
Il confronto tra architetture concorrenti già presenti in <strong>di</strong>versi prodotti, ha portato alla<br />
luce l'importanza <strong>di</strong> separare gli strumenti <strong>per</strong> consentire la concorrenza dai modelli<br />
visibili all'utente. Fondamentale è garantire internamente moduli altamente flessibili e<br />
parametrizzabili, <strong>per</strong> consentire maggiore elasticità nella scelta delle configurazioni a<br />
livello più alto.<br />
Nel presente lavoro si è puntato su questo concetto, creando oggetti che<br />
incapsulassero funzionalità ben separate dell'architettura, e che lasciassero a<strong>per</strong>te<br />
numerose possibilità <strong>di</strong> composizione a livello più alto. Oltre <strong>ad</strong> una classe pensata<br />
<strong>per</strong> isolare e gestire opportunamente i thre<strong>ad</strong> d'esecuzione (EmbeddedThre<strong>ad</strong>), si è<br />
provveduto <strong>ad</strong> isolare quella funzione <strong>di</strong> scheduling che ricorreva spesso nel<br />
modello, facendo anche tutt'uno con i CompositeBehaviour, fornendone un modello<br />
astratto <strong>di</strong> base che può essere specializzato secondo le esigenze (Parallel,<br />
Sequential, FSM, … ).
Conclusioni - Ulteriori Sviluppi 124<br />
Il modulo principale che gestisce le esecuzioni, non è più lo scheduler a livello<br />
agente, ma <strong>di</strong>venta il Thre<strong>ad</strong>Dispatcher, oggetto che fa incontrare Behaviour ed<br />
EmbeddedThre<strong>ad</strong>. Tale modulo amministra situazioni che vanno dal<br />
Single-Thre<strong>ad</strong>ed, al Thre<strong>ad</strong>-<strong>per</strong>-Behaviour, passando <strong>per</strong> il Thre<strong>ad</strong>-Pool in maniera<br />
<strong>di</strong>namica; consente <strong>di</strong> o<strong>per</strong>are scelte sul life-cycle <strong>di</strong> un thre<strong>ad</strong> fissandone, anche a<br />
run-time, tempi massimi d'inattività (da zero <strong>ad</strong> infinito), con<strong>di</strong>zionando quin<strong>di</strong><br />
l'overhe<strong>ad</strong> legato <strong>ad</strong> o<strong>per</strong>azioni <strong>di</strong> creazione/<strong>di</strong>struzione <strong>di</strong> thre<strong>ad</strong>; può gestire in<br />
maniera uniforme più <strong>di</strong> uno scheduler, ma anche essere istanziato più volte e<br />
formare, con i CompositeBehaviour e relativi Scheduler, una gerarchia modulare <strong>di</strong><br />
Dispatcher, ognuno con il proprio thre<strong>ad</strong>-pool <strong>per</strong>sonale.<br />
6.1 Ulteriori Sviluppi<br />
La flessibilità ottenuta può, <strong>per</strong>ò, risultare <strong>di</strong> <strong>di</strong>fficile gestione <strong>per</strong> categorie d'utenti<br />
non particolarmente interessate a preoccuparsi <strong>di</strong> dettagli tecnici. Il confronto tra<br />
DCOM e ORBacus ha insegnato l'importanza <strong>di</strong> fornire, accanto a strumenti<br />
customizzabili, la possibilità <strong>di</strong> poter scegliere tra pochi modelli, calibrati <strong>per</strong><br />
determinate esigenze e <strong>di</strong> facile utilizzo, in modo che con una sola scelta si possa<br />
evitare <strong>di</strong> settare decine <strong>di</strong> parametri <strong>di</strong>fferenti.<br />
Un utile, e necessario, passo successivo potrebbe consistere nel fornire tali modelli<br />
<strong>di</strong> livello su<strong>per</strong>iore, possibilmente dopo un'analisi delle esigenze più sentite dagli<br />
utilizzatori.<br />
Avendo, inoltre, separato le funzionalità essenziali dello scheduler, l'implementazione<br />
<strong>di</strong> specificazioni della classe base si è alquanto semplificata, ed esigenze <strong>di</strong> nuovi<br />
modelli (quali scheduling a priorità o basati su reti <strong>di</strong> Petri) potrebbero esserne<br />
agevolate.<br />
6.2 Ringraziamenti<br />
Volevo porgere un sentito ringraziamento al relatore professor Agostino Poggi ed al<br />
correlatore Giovanni Rimassa innanzi tutto <strong>per</strong> le loro qualità umane, in particolare la
Conclusioni - Ringraziamenti 125<br />
pazienza e comprensione <strong>di</strong>mostrata nei confronti <strong>di</strong> un lavoro che ha visto molti<br />
rallentamenti, <strong>per</strong> la possibilità che mi hanno dato, non solo in questa occasione, <strong>di</strong><br />
svolgere lavori, a mio avviso, interessanti e <strong>per</strong> la capacità <strong>di</strong> stimolarmi ed<br />
accompagnarmi con competenza nello svolgimento <strong>di</strong> questi.<br />
Per gli stessi motivi devo ringraziare quei professori che, nel corso del mio curriculum<br />
scolastico, hanno <strong>di</strong>mostrato <strong>di</strong> essere presenti non solo in cattedra ed hanno unito<br />
capacità professionali, passione ed una grande <strong>di</strong>sponibilità nei confronti degli<br />
studenti. Tra questi vorrei ricordare i professori (e le professoresse) Bocchi,<br />
Capelletti, Caselli, DeFranceschi e Diligenti.<br />
Grazie ai miei genitori, Giuseppe ed Adriana, <strong>per</strong> il loro vivo esempio ed incessante<br />
sostegno; a Ciccio (mio fratello Clau<strong>di</strong>o) <strong>per</strong> la sopportazione (reciproca); ai parenti<br />
tutti <strong>per</strong> l'affetto che ci lega oltre le <strong>di</strong>stanze; <strong>ad</strong> Anita <strong>per</strong> la gioia continua che mi<br />
infonde da ormai un anno; ai vecchi amici (e sono già passati 14 anni), Fina, Galva e<br />
Volpa, ed a Macca, <strong>per</strong>ché finalmente possono festeggiare; agli amici conosciuti a<br />
Parma in questi anni, Stefano, Rosalino, Mirco e Federico, Linda & Co., Gianluigi e<br />
Paolo, <strong>per</strong> l'amicizia e le avventure che ci hanno legato.
Bibliografia 126<br />
7 Bibliografia<br />
Bibl. 1 Wooldridge M., Jennings N. R. - "Intelligent Agents: Theory and Practice"<br />
1994<br />
Bibl. 2 Croft D. W. - "Intelligent Software Agents: Definitions and Applications"<br />
Analytic Services Inc. (ANSER) 1997<br />
Bibl. 3 "FIPA 97 Specification"<br />
Foundation for Intelligent Physical Agents 1997<br />
Bibl. 4 Silberschatz A., Galvin P. - "<strong>Sistemi</strong> O<strong>per</strong>ativi - quarta e<strong>di</strong>zione"<br />
Ad<strong>di</strong>son-Wesley 1995<br />
Bibl. 5 Bellifemine F., Caire G., Rimassa G., Trucco T. - "JADE Programmer’s<br />
GUIDE"<br />
JADE 2.5 - TILab 2002<br />
http://sharon.cselt.it/projects/j<strong>ad</strong>e<br />
Bibl. 6 "Why Are Thre<strong>ad</strong>.stop, Thre<strong>ad</strong>.suspend, Thre<strong>ad</strong>.resume and<br />
Runtime.runFinalizersOnExit Deprecated?"<br />
Java 2 SDK, Standard E<strong>di</strong>tion Documentation v.1.2.2 - Sun Microsystem<br />
Inc.1999<br />
file:///JDK/docs/guide/misc/thre<strong>ad</strong>PrimitiveDeprecation.html<br />
Bibl. 7 "What is Middleware"<br />
IONA Technologies 2001<br />
http://www.ooc.com/corba/what_is_middleware.html
Bibliografia 127<br />
Bibl. 8 "DCOM Technical Overview"<br />
Microsoft Corporation 1996<br />
http://msdn.microsoft.com/library/default.asp?url=/library/en-<br />
us/dndcom/html/msdn_dcomtec.asp<br />
Bibl. 9 "DCOM Architecture"<br />
Microsoft Corporation 1997<br />
http://msdn.microsoft.com/library/default.asp?url=/library/en-<br />
us/dndcom/html/msdn_dcomtec.asp<br />
Bibl. 10 "ORB Basics"<br />
Object Management Group Inc. 2001<br />
http://www.omg.org/gettingstarted/orb_basics.htm<br />
Bibl. 11 "CORBA FAQ"<br />
Object Management Group Inc. 2001<br />
http://www.omg.org/gettingstarted/corbafaq.htm<br />
Bibl. 12 "Common Object Request Broker Architecture (CORBA), v.2.6"<br />
Object Management Group Inc. 2001<br />
http://www.omg.org/technology/documents/formal/corba_2.htm<br />
Bibl. 13 "Common Object Request Broker Architecture (CORBA), v.2.6"<br />
capitolo 11: "Portable Object Adapter" - Object Management Group Inc.<br />
2001<br />
http://www.omg.org/cgi-bin/doc?formal/01-12-49<br />
Bibl. 14 "ORBacus for C++ and Java v. 4.0.3"<br />
capitolo 18: "Concurrency Models" - Object Oriented Concept Inc. 2000<br />
http://www.ooc.com
Bibliografia 128<br />
Bibl. 15 "JavaBeans FAQ: General Questions"<br />
Sun Microsystems Inc. 2002<br />
http://java.sun.com/products/javabeans/faq/faq.general.html<br />
Bibl. 16 "FAQ: Enterprise JavaBean"<br />
Sun Microsystems Inc. 2002<br />
http://java.sun.com/products/javabeans/faq/faq.enterprise.html<br />
Bibl. 17 "EJB Tutorial - An overview of EJB"<br />
http://www.ejbtut.com/Overview.jsp<br />
Bibl. 18 DeMichiel L. G. - "Enterprise JavaBeans Specification version 2.0, final<br />
release"<br />
ejb-2_0-fr2-spec.pdf - capitoli 4,7 e 24 - Sun Microsystems Inc. 2001<br />
http://java.sun.com/products/ejb/docs.html<br />
Bibl. 19 Gopalan Suresh Raj - "EJB Servers"<br />
1998<br />
http://www.execpc.com/~gopalan/java/ejb/ejbserv.html<br />
Bibl. 20 Scallan T. - "Assuring Reliability of Enterprise JavaBean Applications"<br />
Segue Software 1999<br />
http://www.omg.org/news/whitepa<strong>per</strong>s/segueejb.pdf<br />
Bibl. 21 Gopalan Suresh Raj - "A Detailed Comparison of CORBA, DCOM and<br />
Java/RMI (with specific code examples)"<br />
1998<br />
http://www.execpc.com/~gopalan/misc/compare.html
Bibliografia 129<br />
Bibl. 22 DISA, JIEO, and JEXF - "Recommendations for Using DCE, DCOM, and<br />
CORBA Middleware"<br />
Mitre Corporation 1998 - Mitre Document ID: MITRE-DAS-C1<br />
http://<strong>di</strong>icoe.<strong>di</strong>sa.mil/coe/atd/das_all.pdf<br />
Bibl. 23 Santiago Comella-Dorda - "Component Object Model (COM), DCOM, and<br />
Related Capabilities"<br />
Carnegie Mellon University 2001<br />
http://www.sei.cmu.edu/str/descriptions/com_body.html