28.05.2013 Views

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 ...

SHOW MORE
SHOW LESS

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

Hooray! Your file is uploaded and ready to be published.

Saved successfully!

Ooh no, something went wrong!