PROGRAMMAZIONE CONCORRENTE IN JAVA



Похожие документы
Monitor. Introduzione. Struttura di un TDA Monitor

Realizzazione di Politiche di Gestione delle Risorse: i Semafori Privati

Il costrutto monitor [Hoare 74]

Java Virtual Machine

Java threads (2) Programmazione Concorrente

Tipi primitivi. Ad esempio, il codice seguente dichiara una variabile di tipo intero, le assegna il valore 5 e stampa a schermo il suo contenuto:

Ottava Esercitazione. introduzione ai thread java mutua esclusione

Il costrutto monitor [Hoare 74]

Esercizi sul Monitor in Java

Sistemi Operativi. Lez. 13: primitive per la concorrenza monitor e messaggi

Esercitazioni 7 e 8. Bounded Buffer con sincronizzazione Java (1)

Pronto Esecuzione Attesa Terminazione

Soluzione dell esercizio del 2 Febbraio 2004

SAPIENZA Università di Roma Facoltà di Ingegneria dell Informazione, Informatica e Statistica

SOMMARIO Coda (queue): QUEUE. QUEUE : specifica QUEUE

Multithreading in Java. Fondamenti di Sistemi Informativi

SISTEMI OPERATIVI. Deadlock (blocco critico) Domande di verifica. Luca Orrù Centro Multimediale Montiferru 04/06/2007

Un esercizio d esame. Flavio De Paoli

12 - Introduzione alla Programmazione Orientata agli Oggetti (Object Oriented Programming OOP)

GESTIONE DEI PROCESSI

1. Che cos è la multiprogrammazione? Si può realizzare su un sistema monoprocessore? 2. Quali sono i servizi offerti dai sistemi operativi?

Sistemi Operativi Esercizi Sincronizzazione

Programmazione concorrente in Java. Dr. Paolo Casoto, Ph.D

Corso di Informatica

FONDAMENTI di INFORMATICA L. Mezzalira

Con il termine Sistema operativo si fa riferimento all insieme dei moduli software di un sistema di elaborazione dati dedicati alla sua gestione.

Terza Esercitazione. Unix - Esercizio 1. Unix System Call Exec Java Introduzione Thread

Realizzazione di una classe con un associazione

Oggetti Lezione 3. aspetti generali e definizione di classi I

Informatica 3. Informatica 3. LEZIONE 6: Il controllo dell esecuzione. Lezione 6 - Modulo 1. Errori durante l esecuzione. Il controllo dell esecuzione

La struttura dati ad albero binario

Sistemi Operativi GESTIONE DELLA MEMORIA SECONDARIA. D. Talia - UNICAL. Sistemi Operativi 11.1

Sistemi Operativi. Memoria Secondaria GESTIONE DELLA MEMORIA SECONDARIA. Struttura del disco. Scheduling del disco. Gestione del disco

Algoritmi di Ricerca. Esempi di programmi Java

Funzioni in C. Violetta Lonati

progam ponteasensounicoalaternato ; type dir = ( nord, sud );

Progettazione : Design Pattern Creazionali

Prova di Laboratorio di Programmazione

Registratori di Cassa

Corso di Sistemi Operativi Ingegneria Elettronica e Informatica prof. Rocco Aversa. Raccolta prove scritte. Prova scritta

Corso di Laurea in Ingegneria Gestionale Esame di Informatica a.a settembre 2011

CIRCOLARE RILEVA WEB del 19/05/2009

Il descrittore di processo (PCB)

ACCESSO AL SISTEMA HELIOS...

Un sistema operativo è un insieme di programmi che consentono ad un utente di

(Esercizi Tratti da Temi d esame degli ordinamenti precedenti)

Scheduling della CPU. Sistemi multiprocessori e real time Metodi di valutazione Esempi: Solaris 2 Windows 2000 Linux

LA SINCRONIZZAZIONE TRA PROCESSI

Thread: sincronizzazione Esercitazioni del 09 Ottobre 2009

I file di dati. Unità didattica D1 1

Deadlock (stallo) Parte III. Deadlock

Inizializzazione, Assegnamento e Distruzione di Classi

Java: Compilatore e Interprete

APPUNTI DI MATEMATICA LE FRAZIONI ALGEBRICHE ALESSANDRO BOCCONI

5.2.1 RELAZIONI TRA TABELLE Creare una relazione uno-a-uno, uno-a-molti tra tabelle 9

Telematica II 17. Esercitazione/Laboratorio 6


Programmazione concorrente in Java

appunti delle lezioni Architetture client/server: applicazioni server

Corso di Linguaggi di Programmazione

Esercizi sugli Oggetti Monitor

Esercitazioni di Progettazione del Software. Esercitazione (Prova al calcolatore del 17 settembre 2010)

Corso di Informatica

Computazione multi-processo. Condivisione, Comunicazione e Sincronizzazione dei Processi. Segnali. Processi e Threads Pt. 2

Matematica in laboratorio

Synchronized (ancora)

Mac Application Manager 1.3 (SOLO PER TIGER)

Esercitazione finale per il corso di Sistemi Operativi (A.A. 2004/2005)

LE FUNZIONI A DUE VARIABILI

Gestione Turni. Introduzione

Gestione della memoria centrale

Esercizi Capitolo 6 - Alberi binari di ricerca

Programmazione a Oggetti Lezione 10. Ereditarieta

Modulo 4: Ereditarietà, interfacce e clonazione

LABORATORIO DI PROGRAMMAZIONE EDIZIONE 1, TURNO B

COLLI. Gestione dei Colli di Spedizione. Release 5.20 Manuale Operativo

Siti web centrati sui dati Architettura MVC-2: i JavaBeans

10 - Programmare con gli Array

Linguaggi Corso M-Z - Laurea in Ingegneria Informatica A.A Esercitazione. Programmazione Object Oriented in Java

Memoria secondaria. Struttura del disco. Scheduling del disco. Gestione dell unità a disco. Affidabilità dei dischi: RAID

Introduzione ai Metodi Formali

B+Trees. Introduzione

Sistemi Operativi. Lezione 7 Comunicazione tra processi

La gestione dell input/output da tastiera La gestione dell input/output da file La gestione delle eccezioni

MANUALE ESSE3 Gestione Registro delle lezioni

Esercizio 1: trading on-line

Istituto Centrale per il Catalogo Unico delle Biblioteche Italiane. e per le Informazioni bibliografiche. Manuali utente per SBN WEB. Versione 1.

Consiglio regionale della Toscana. Regole per il corretto funzionamento della posta elettronica

Risolvere un problema significa individuare un procedimento che permetta di arrivare al risultato partendo dai dati

2.0 Gli archivi. 2.1 Inserire gli archivi. 2.2 Archivio Clienti, Fornitori, Materiali, Noleggi ed Altri Costi. Impresa Edile Guida all uso

Nuova procedura di Cassa Contanti Wingesfar: istruzioni per le farmacie Novembre 2009

Capitolo 7: Sincronizzazione

Introduzione alla programmazione in C

Il problema del produttore e del consumatore. Cooperazione tra processi

Architettura MVC-2: i JavaBeans

Tricks & Tips. [Access] Tutorial - ActiveX - Controllo Tree View. - Michele de Nittis - Versione: 1 Data Versione: venerdì 30 agosto 2002

Corso di Laurea Ingegneria Informatica Fondamenti di Informatica 2

Транскрипт:

PROGRAMMAZIONE CONCORRENTE IN JAVA Supporto per il monitor con condition esplicite Al fine di facilitare l uso del monitor di Hoare in Java, sono utili le seguenti classi e interfacce appartenenti al package sde.monitor. public interface Condition{ public void Wait(); public void Signal(); //Condition public interface ConditionP{ public void Wait( int priority ); public void Signal(); //ConditionP public final class Monitor{ private SemaforoBinario mutex=new SemaforoBinario(1); private SemaforoBinario urgent=new SemaforoBinario(0); private int urgentcount=0; public void Enter(){ mutex.p(); //Enter public void Exit(){ if( urgentcount > 0 ) urgent.v(); else mutex.v(); //Exit public Condition newcondition(){ return new ConditionVariable(); private class ConditionVariable implements Condition{ private SemaforoBinario xsem=new SemaforoBinario(0); private int xcount=0; public void Wait() { xcount++; if( urgentcount>0 ) urgent.v(); else mutex.v(); xsem.p(); xcount--; //Wait public void Signal() { if( xcount>0 ) { urgentcount++; xsem.v(); urgent.p(); urgentcount--; 40

//Signal //ConditionVariable public ConditionP newconditionp(){ return new ConditionVariablePriorited(); private class ConditionVariablePriorited implements ConditionP{ private SemaforoBinarioAPriorita xsem=new SemaforoBinarioAPriorita(0); private int xcount=0; public void Wait( int priority ) { xcount++; if( urgentcount>0 ) urgent.v(); else mutex.v(); xsem.p( priority ); xcount--; //Wait public void Signal() { if( xcount>0 ) { urgentcount++; xsem.v(); urgent.p(); urgentcount--; //Signal //ConditionVariablePriorited //Monitor La classe Monitor e l interfaccia Condition rendono automatica la dichiarazione di semafori e associati contatori, in tal modo semplificando al massimo le operazioni di programmazione. Una condition appartiene ad un monitor. La creazione di una condition si ottiene invocando il metodo newcondition() su un oggetto monitor. Di seguito si riassumono le convenzioni che il programmatore deve rispettare per un uso corretto dei monitor in Java. Un monitor si programma come una classe passiva che dichiara come variabili di istanza una variabile di tipo Monitor, es. mon, e tante variabili condition quante ne servono per il monitor. Ciascuna entry procedure va strutturata come segue: //procedure entry ep() public /*entry*/ void ep(){ mon.enter(); corpo di ep; mon.exit(); //ep; 41

Detta x una variabile condition, le relative operazioni wait e signal sono evocate semplicemente come x.wait() e x.signal() rispettivamente. Come si vede le formalità sono ridotte all osso ed il rischio di commettere errori, rispetto alle espansioni in linea, minimizzato. Per entry procedure funzionali, cioè che restituiscono esplicitamente un risultato, bisogna prestare attenzione che la mon.exit() va invocata prima dell istruzione return. Sul problema dei lettori-scrittori È stato già affrontato in precedenza mostrando una soluzione (basata sui semafori) che dà priorità ai lettori. Se la risorsa è libera, essa è attribuita ad uno scrittore od un lettore. Mentre un lettore è in accesso, eventuali altri lettori in arrivo guadagnano immediatamente l accesso alla risorsa. Mentre uno scrittore è in accesso, tutti gli altri processi devono aspettare. Uno scrittore in attesa è svegliato dall ultimo lettore che abbandona la risorsa o da uno scrittore che termini l accesso. Tale soluzione crea evidentemente il pericolo di starvation degli scrittori. Per comodità, si propone una classe LettoriScrittori che introduce il vincolo per un thread di chiedere l autorizzazione ad effettuare un operazione (es. iniziolettura etc.) e a segnalarne la conclusione (es. finelettura). public class LettoriScrittori { //Ammette la possibilita' di starvation degli scrittori SemaforoBinario mutex_scrittori=new SemaforoBinario( 1 ); SemaforoBinario mutex_lettori=new SemaforoBinario( 1 ); int contatore_lettori=0; public void iniziolettura(){ mutex_lettori.p(); contatore_lettori++; if( contatore_lettori == 1 ) mutex_scrittori.p(); mutex_lettori.v(); public void finelettura(){ mutex_lettori.p(); contatore_lettori--; if( contatore_lettori == 0 ) mutex_scrittori.v(); mutex_lettori.v(); public void inizioscrittura(){ mutex_scrittori.p(); public void finescrittura(){ mutex_scrittori.v(); //LettoriScrittori 42

Di seguito si presenta un altra soluzione, strutturata con monitor e condition esplicite, in grado di evitare la starvation. Essa è basata sulle seguenti assunzioni: iniziolettura() pone il lettore in wait se uno scrittore è in fase di accesso o se almeno uno scrittore è già in wait; diversamente consente l accesso al lettore e sveglia immediatamente altri lettori in attesa finelettura() abilita gli scrittori eventualmente in attesa inizioscrittura() pone il processo scrittore in wait se esistono lettori in attesa o un altro scrittore è già in fase di accesso finescrittura() sblocca la risorsa e sveglia eventuali processi lettori in attesa o, in alternativa, altri scrittori in attesa. Il monitor utilizza i seguenti dati: lettori (iniz 0), condition di attesa per i lettori scrittori (iniz 0), condition di attesa per gli scrittori lettoriinattesa (iniz 0), contatore dei lettori in wait su read-queue scrittoriinattesa (iniz 0), contatore degli scrittori in wait su write-queue scrittoreinaccesso (iniz false), booleano che indica se uno scrittore è già in fase di accesso lettoriinaccesso (iniz 0), contatore dei lettori in fase di accesso. //file LettoriScrittori.java package sde.lettoriscrittori; import sde.monitor.*; public class LettoriScrittori { private Monitor monitor=new Monitor(); private Condition lettori=monitor.newcondition(); private Condition scrittori= monitor.newcondition(); private int lettoriinattesa=0; private int scrittoriinattesa=0; private int lettoriinaccesso=0; private boolean scrittoreinaccesso=false; public void iniziolettura(){ monitor.enter(); if( scrittoreinaccesso scrittoriinattesa > 0 ){ lettoriinattesa++; lettori.wait(); lettoriinattesa--; lettoriinaccesso++; lettori.signal(); monitor.exit(); 43

public void finelettura(){ monitor.enter(); lettoriinaccesso--; if( lettoriinaccesso == 0 ) scrittori.signal(); monitor.exit(); public void inizioscrittura(){ monitor.enter(); if( lettoriinaccesso > 0 scrittoreinaccesso ){ scrittoriinattesa++; scrittori.wait(); scrittoriinattesa--; scrittoreinaccesso=true; monitor.exit(); public void finescrittura(){ monitor.enter(); scrittoreinaccesso=false; if( lettoriinattesa > 0 ) lettori.signal(); else scrittori.signal(); monitor.exit(); //LettoriScrittori 44

Sul problema dei cinque filosofi È stata a suo tempo discussa un implementazione basata su monitor che evita il deadlock. A questo proposito si ricorda che l idea è di far richiedere ad un filosofo che intenda mangiare entrambe le forchette con un azione indivisibile. Si osserva qui che tale soluzione, mentre evita il deadlock non impedisce la starvation di un filosofo. Infatti è possibile, ad esempio, che i filosofi 0 e 3 ottengano le forchette e mangino, che il filosofo 4 voglia mangiare, che il filosofo 0 deponga le forchette, che il filosofo 0 richieda nuovamente le forchette e le ottenga in quanto il filosofo 3 non ha ancora rilasciato le sue e cosi via. Di seguito si fornisce una soluzione che evita deadlock e starvation e utilizza i seguenti dati: un array fork di 5 elementi (uno per filosofo), che contiene di momento in momento il numero di forchette a disposizione di ciascun filosofo un array count di 5 elementi (uno per filosofo), che contiene il contatore delle volte che ciascun filosofo ha mangiato La starvation è evitata assegnando ad un filosofo le due forchette richieste solo nel caso che entrambi i filosofi vicini non abbiano mangiato meno volte. In altre parole, dal momento che un filosofo emette la richiesta delle forchette, esso sarà costretto ad attendere o perchè non sono disponibili entrambe le forchette, o perchè il filosofo ha mangiato sino ad ora più volte dei suoi vicini. Si presentano due versioni della soluzione organizzate in una classe Manager che controlla il comportamento dei filosofi. La prima versione utilizza direttamente i meccanismi propri di Java (wait() e notify()). La seconda, invece, si basa su monitor e condition espliciti. Le variabili locali sin e des denotano i due filosofi vicini a quello corrente who. Si presti attenzione alla costruzione di oggetti array attraverso un aggregato di inizializzazione (new è implicita in questi casi). // file Manager.java package sde.filosofi; public class Manager{ private byte fork[]={ 2, 2, 2, 2, 2 ; private int count[]={ 0, 0, 0, 0, 0 ; public synchronized void getfork( int who ) { int sin=(who+4)%5, des=(who+1)%5; //filosofi adiacenti a who while( fork[who]<2 ( (count[who]>count[sin]) (count[who]>count[des]) ) ) { if( fork[who]<2 ) System.out.println("Filosofo "+who+" attende forchette"); else System.out.println("Filosofo "+who+" attende per fairness"); 45

try { wait(); catch(interruptedexception e) { fork[sin]--; fork[des]--; ;//getfork public synchronized void putfork( byte who ) { System.out.println("Filosofo "+who+" rilascia forchette"); int sin=(who+4)%5, des=(who+1)%5; fork[sin]++; fork[des]++; count[who]++; notifyall(); ;//putfork //Manager La struttura di un generico filosofo è mostrata di seguito: public class Filosofo extends Thread { private byte io; private Manager m; public Filosofo( byte io, Manager m ){ this.io=io; this.m=m; public void run() { while(true){ System.out.println("Filosofo "+io+" pensa..."); try{ sleep( (int)(math.random()*5000 ) ); catch(interruptedexception e) {; m.getfork( io ); System.out.println("Filosofo "+io+" mangia..."); try{ sleep( (int)(math.random()*5000 ) ); catch(interruptedexception e) {; m.putfork( io ); //run //Filosofo Una possibile classe di test è la seguente: // file T5F.java import sde.filosofi.*; public class T5F{ public static void main(string args[]) { Manager m=new Manager(); Filosofo f[]=new Filosofo[5]; for(byte i=0;i<f.length;i++) { 46

f[i]=new Filosofo( i, m ); f[i].start(); //T5F Si presenta ora una differente implementazione della classe Manager fondata sui concetti di monitor e condition. Nessuna modifica è richiesta alle classi Filosofo e T5F. //file Manager.java package sde.filosofi; import sde.monitor.*; public class Manager{ private byte fork[]={ 2, 2, 2, 2, 2 ; private int count[]={ 0, 0, 0, 0, 0 ; private Monitor monitor=new Monitor(); private Condition Fil[]={ monitor.new Condition(), monitor.new Condition(), monitor.new Condition(), monitor.new Condition(), monitor.new Condition() ; public void getfork( int who ) { monitor.enter(); int sin=(who+4)%5, des=(who+1)%5; while( fork[who]<2 ((count[who]>count[sin]) (count[who]>count[des])) ) { if( fork[who]<2 ) System.out.println("Filosofo "+who+" attende forchette"); else System.out.println("Filosofo "+who+" attende per fairness"); Fil[who].Wait(); fork[sin]--; fork[des]--; monitor.exit(); ;//getfork public void putfork( byte who ) { monitor.enter(); System.out.println("Filosofo "+who+" rilascia forchette"); int sin=(who+4)%5, des=(who+1)%5; fork[sin]++; fork[des]++; count[who]++; 47

if( fork[sin]==2 ) Fil[sin].Signal(); if( fork[des]==2 ) Fil[des].Signal(); monitor.exit(); ;//putfork //Manager Nella nuova formulazione della classe Manager è stato introdotto un array Fil di condition, una condition per filosofo, ed una variabile Monitor. Allorquando un filosofo non ha entrambe le forchette o comunque deve attendere per ragioni di fairness, si pone in wait sulla sua condition. Gestione di buffer pool per grandi quantità di dati Si tratta di un estensione del problema produttore-consumatore-buffer al caso in cui ogni elemento di dato da trasferire dal produttore al consumatore è di grosse proporzioni. Per migliorare l efficienza dell applicazione, si cerca di evitare che il dato venga prima generato su un area dati locale del produttore, quindi copiato dal produttore sul buffer, quindi dal buffer ad un area dati locale del consumatore per consentirne il consumo. A questo proposito si utilizza un pool di buffer liberi. Il produttore genera un dato riempiendo un buffer libero del pool. Quindi trasmette al consumatore l indirizzo o indice del pool in cui c è il dato. Lo schema è utilizzabile tra più coppie <prod,cons> collegate ciascuna da un diverso buffer circolare, i cui elementi sono indici del pool di buffer liberi condiviso tra tutte le coppie. Q1 P1 C1 Q2 P2 C2... Qi Pi Ci Pool di buffer liberi Il comportamento di un generico produttore/consumatore è schematizzato in pseudo codice di seguito: produttore: while(true) { 48

acquisisce un buffer dal pool e annota il suo indirizzo riempie il buffer aggiunge l indirizzo del buffer nella coda condivisa col consumatore consumatore: while(true) { rimuove dalla coda condivisa col produttore l indirizzo di un buffer svuota il buffer del pool rende libero l indirizzo del buffer utilizzato Ovviamente, operazioni di sincronizzazione e mutua esclusione vanno adottate sia tra produttore e consumatore nei riguardi della coda condivisa Qi, che nei confronti del pool di buffer liberi condiviso tra tutte le coppie. Si può programmare una classe monitor Gestore di una generica Qi, con operazioni get e put. Il monitor va quindi instanziato per ogni coppia <prod,cons>. Tale classe Gestore è identica alla classe BoundedBuffer vista in precedenza. Un altro monitor, Allocatore di risorse, con operazioni Acquisisci e Rilascia, va programmato per la gestione del pool. Un produttore potrebbe essere costretto ad attendere se un buffer libero non esiste nel pool. In realtà, il monitor Allocatore potrebbe mantenere dentro di sè un array Pool di indirizzi alle risorse (buffer) fisiche esistenti all esterno del monitor. Tale array andrebbe inizializzato con gli indirizzi dei vari buffer liberi esterni. In termini Java si ha: import sde.monitor.*; public class Allocatore { int[] Pool; int size, testa; Monitor monitor=new Monitor(); Condition bufferlibero=monitor.newcondition(); public Allocator( int[] addr ){ Pool=new int[addr.length]; size=addr.length; for(int i=0; i<pool.length; i++) Pool[i]=addr[i]; testa=size-1; //Allocatore 49

public int Acquisisci() { monitor.enter(); if(testa==-1) bufferlibero.wait(); int buffloc=pool[testa ]; monitor.exit(); return buffloc; //Acquisisci public void Rilascia( int buffloc ) { monitor.enter(); Pool[++testa]=buffloc; bufferlibero.signal(); monitor.exit(); ;//Rilascia //Allocatore Come si vede, Pool è gestito semplicemente a stack. Schedulatore del movimento della testina di un disco Il disco di un sistema di calcolo è una delle unita di I/O più importanti. Dall efficienza degli accessi al disco dipendono le prestazioni dell intero sistema. In quanto segue si discute un algoritmo di scheduling del movimento della testina capace di ottimizzare le prestazioni evitando starvation delle richieste. Il tempo di accesso al disco consiste in generale di tre contributi: tempo di seek (o di posizionamento) tempo di latenza (o di rotazione) tempo di trasferimento Il tempo di posizionamento è quello richiesto per spostare il braccio mobile con la testina di lettura/scrittura sulla traccia interessata dall operazione di accesso (si ricorda che un indirizzo su disco consiste di <numero di traccia, numero di settore nella traccia>. Dopo questo tempo, occorre attendere il ritardo di latenza per far giungere il settore sotto la testina. Statisticamente questo tempo può essere assunto pari alla metà del tempo di rotazione del disco. Segue infine il tempo di trasferimento che dipende evidentemente dall ammontare dell informazione in gioco. Dei tre tempi, quello iniziale di posizionamento è fondamentale ed è oggetto di controllo con un assegnato algoritmo di scheduling dei movimenti della testina. In un caso possibile, la testina potrebbe essere spostata, rispetto alla posizione corrente, sulla traccia più vicina in base alle richieste pendenti di accesso. 50

Quest algoritmo comporterebbe il rischio di ritardare indefinitamente alcune richieste. In alternativa, si potrebbe far muovere la testina da un bordo all altro del disco, attuando le richieste pendenti corrispondenti alle varie posizioni raggiunte, quindi invertendo il senso di spostamento ed esaudendo le altre richieste (algoritmo SCAN), o ripartendo di nuovo dall estremo iniziale (algoritmo C-SCAN). Un altra possibilità sarebbe quella di far muovere la testina non da un estremo all altro sistematicamente, ma limitando gli spostamenti alle ultime tracce, in ogni senso di marcia, oggetto di richiesta (gli algoritmi sono denominati rispettivamente LOOK e C-LOOK). In quanto segue si prende in considerazione un algoritmo di scheduling tipo LOOK: si fa muovere la testina nei due sensi di moto e durante ogni senso si eseguono le richieste. Si introduce un monitor DiskManager che esporta due procedure entry richiesta(traccia-dest:int) rilascio(), da chiamare a completamento di una operazione di I/O su disco. Un processo che richieda un accesso al disco dovrebbe seguire il protocollo: controllore-disco.richiesta( numero traccia ); legge o scrive sul disco alla traccia richiesta controllore-disco.rilascio; Il monitor in ogni momento può essere occupato a servire una richiesta. In questo caso una nuova richiesta va posta in wait. Il particolare senso di marcia in corso, se in (da traccia 1 a max traccia) o out, costituisce una variabile di stato. Inoltre servono due variabili condition, diciamole motoin e motoout su cui accodare appropriatamente le richieste, con annessi contatori count-motoin e count-motoout. Cura particolare va posta sulla gestione delle richieste che sopraggiungono dinamicamente e che si riferiscono alla posizione corrente della testina. Accettando di servirle subito, si potrebbe generare starvation di altre richieste. È opportuno quindi porre in wait queste richieste in modo da poterle obbedire al prossimo cambio di senso di moto. È evidente l interesse di schedulare le richieste in modo tale da riprenderle in base alla minima vicinanza alla posizione corrente. A questo scopo si suppone di utilizzare uno schema di monitor con condition con wait a priorità. La traccia destinazione di una richiesta può essere utilizzata come priorità: più piccolo è il suo valore, maggiore è la priorità. Nel senso di moto in, è ovvio che la successione delle richieste pendenti sulla condition motoin è utile che sia per traccia crescente. Su 51

motoout, invece, è opportuno far trovare per prime le richieste di traccia più grande. Dunque come priorità è utilizzabile, in questo caso, la differenza max_traccia - traccia richiesta. Segue una formulazione della classe DiskManager. //file DiskManager.java package sde.diskmanager; import sde.monitor.*; public class DiskManager { private final int max_traccia; private final int in=0, out=1; private int direzione=in; private boolean occupato=false; private int posizione=1; private Monitor m=new Monitor(); private ConditionP motoin=m.newconditionp(); private ConditionP motoout= m.newconditionp(); private int count_motoin=0, count_motoout=0; public DiskManager( int max_traccia ){ this.max_traccia=max_traccia; public void richiesta( int numero_traccia ){ m.enter(); if( occupato ){ if( posizione < numero_traccia posizione == numero_traccia && direzione == out ){ count_motoin++; motoin.wait( numero_traccia ); count_motoin--; else { count_motoout++; motoout.wait( max_traccia - numero_traccia ); count_motoout--; else{ //fissa senso di moto if( posizione <= numero_traccia ) direzione=in; else direzione=out; occupato=true; posizione=numero_traccia; m.exit(); //richiesta public void rilascio(){ m.enter(); occupato=false; if( direzione==in) if( count_motoin==0 ){ direzione=out; motoout.signal(); 52

else motoin.signal(); else if( count_motoout==0 ){ direzione=in; motoin.signal(); else motoout.signal(); m.exit(); //rilascio //DiskManager Si nota che il metodo richiesta schedula richieste per la traccia corrente sulla condition associata con il senso di moto opposto a quello corrente. Ad ogni modo, procedendo in un certo senso di marcia si processano tutte le richieste comunque presenti per il senso di moto. Richieste per una stessa traccia sono processate in ordine di arrivo FCFS. Il metodo richiesta, se invocato mentre occupato=false, provvede a fissare il senso di moto. La classe DiskManager richiede la disponibilità di monitor con condition a priorità (ConditionP). L implementazione di questo tipo di condition (si riveda la classe Monitor fornita in precedenza) dipende dalla classe semaforo binario a priorità richiamata di seguito. //file SemaforoBinarioAPriorita.java package sde.semaforo; import java.util.*; class Tripla{ Thread id; int priority; boolean sveglia; Tripla( Thread id, int priority, boolean sveglia ){ this.id=id; this.priority=priority; this.sveglia=sveglia; public class SemaforoBinarioAPriorita{ private Vector codaapriorita=new Vector(); private int contatore; public SemaforoBinarioAPriorita( int contatore ) throws BadInitializationException { if( contatore<0 contatore>1 ) throw new BadInitializationException(); this.contatore=contatore; public synchronized void P( int priority ){ 53

if( contatore==0 ){ if( codaapriorita.size()>0 && ((Tripla)codaAPriorita.get(0)).sveglia && priority < ((Tripla)codaAPriorita.get(0)).priority ){ //preemption sotto una V in corso int i; for( i=0; i<codaapriorita.size(); i++ ) if(!((tripla)codaapriorita.get(i)).sveglia ) break; ((Tripla)codaAPriorita.get(i-1)).sveglia=false; else { //inserisci in ordine questo thread su codaapriorita int pos=0; while( pos<codaapriorita.size() && ((Tripla)codaAPriorita.get(pos)).priority<=priority ) pos++; codaapriorita.add( pos, new Tripla( Thread.currentThread(), priority, false) ); while( true ){ try{ wait(); catch( InterruptedException e ){; Tripla t=(tripla)codaapriorita.get(0); if( Thread.currentThread()==t.id && t.sveglia ) { codaapriorita.remove(0); if( codaapriorita.size()>0 && ((Tripla)codaAPriorita.get(0)).sveglia ) notifyall(); break; contatore=0; public synchronized void V(){ if( codaapriorita.size()==0 ) contatore=1; else{ int i; for( i=0; i<codaapriorita.size(); i++ ) if(!((tripla)codaapriorita.get(i)).sveglia ) break; if( i<codaapriorita.size() ) ((Tripla)codaAPriorita.get(i)).sveglia=true; notifyall(); //SemaforoBinarioAPriorita La classe SemaforoBinarioAPriorita mantiene una lista codaapriorita in cui un thread che esegua P(p) e si sospenda dà luogo ad un nodo che occupa un posto fissato dalla priorità p. Ovviamente dal punto di vista Java l ordine di attesa è 54

diverso. Al tempo di una V si sveglia il thread in testa alla coda ponendo il campo sveglia del relativo nodo a true. Tutti gli altri thread in attesa hanno il campo sveglia a false. E possibile una situazione di preemption che si verifica allorquando, al tempo di una P, il thread richiedente trova il semaforo rosso e dovrebbe entrare in attesa. Tuttavia il nuovo thread ha priorità massima ed il thread in testa a codaapriorita è stato flaggato come da risvegliare. In queste condizioni, il thread in testa alla lista viene costretto a riaddormentarsi mentre il thread a massima priorità entra in sezione critica. Gli altri dettagli dovrebbero essere auto-esplicativi. L interfaccia ConditionP è identica a Condition salvo che ora utilizza SemaforoBinarioAPriorita. Nessuna modifica è richiesta a Monitor.java. 55

Casi di studio Buffer limitato generico Si ripresenta per comodità una soluzione al problema del buffer limitato che accetta come elementi valori di tipo Object. Per generalità si propone una classe astratta BufferLimitato e si forniscono tre implementazioni rispettivamente basate sui semafori (BufferLimitatoS), il monitor di Hoare (BufferLimitatoMH) ed il monitor nativo di Java (BufferLimitatoJS). Tutte le classi sono parte del package sde.buffer. Anche il programma di prova TB.java è inserito nello stesso package. Una classe astratta e generica BufferLimitato (Java 1.5) //file BufferLimitato.java package sde.buffer; public abstract class BufferLimitato<T>{ protected int n; protected T [] buffer; protected int in=0, out=0, count=0; public BufferLimitato( int n ){ this.n=n; buffer=(t[])new Object[n]; public abstract T get(); public abstract void put( T value ); //BufferLimitato Una classe BufferLimitatoS basata sui semafori //file BufferLimitatoS.java package sde.buffer; import sde.semaforo.*; public class BufferLimitatoS<T> extends BufferLimitato<T>{ protected SemaforoBinario mutex=new SemaforoBinario(1); protected SemaforoContatore empty; protected SemaforoContatore full; public BufferLimitatoS( int n ){ super(n); empty=new SemaforoContatore(n); full=new SemaforoContatore(0); 56

public T get(){ full.p(); mutex.p(); T ans=buffer[out]; out=(out+1)%n; count--; mutex.v(); empty.v(); return ans; //get public void put( T value ) { empty.p(); mutex.p(); buffer[in]=value; in=(in+1)%n; count++; mutex.v(); full.v(); //put //BufferLimitatoS Una classe BufferLimitatoMH basata sul monitor di Hoare //file BufferLimitatoMH.java package sde.buffer; import sde.monitor.*; public class BufferLimitatoMH<T> extends BufferLimitato<T> { protected Monitor m=new Monitor(); protected Condition nonempty=m.newcondition(); protected Condition nonfull=m.newcondition(); public BufferLimitatoMH( int n ){ super(n); public T get() { m.enter(); if( count==0 ) nonempty.wait(); T ans=buffer[out]; out=(out+1)%n; count--; nonfull.signal(); m.exit(); return ans; //get public void put( T value ) { m.enter(); if( count==n ) nonfull.wait(); buffer[in]=value; in=(in+1)%n; count++; nonempty.signal(); m.exit(); //put //BufferLimitatoMH 57

Una classe BufferLimitatoJS basata sul monitor nativo di Java //file BufferLimitatoJS.java package sde.buffer; public class BufferLimitatoJS<T> extends BufferLimitato<T> { public BufferLimitatoJS( int n ){ super(n); public synchronized T get() { while( count==0 ) try { wait(); catch (InterruptedException ignored) { T ans=buffer[out]; out=(out+1)%n; count--; notifyall(); return ans; //get public synchronized void put( T value ) { while( count==n ) try { wait(); catch (InterruptedException ignored) { buffer[in]=value; in=(in+1)%n; count++; notifyall(); //put //BufferLimitatoJS Un programma di prova TB.java //file TB.java package sde.buffer; class Producer extends Thread{ int id; int seed; BufferLimitato<Integer> bl; public Producer( int id, int seed, BufferLimitato<Integer> bl ){ this.id=id; this.seed=seed; this.bl=bl; public void run(){ while( true ){ System.out.println("Producer#"+id+" produces item# "+seed); bl.put( new Integer( seed ) ); //oppure: bl.put( seed ); //autoboxing dei tipi di base seed++; try{ sleep( (int)(math.random()*500 ) ); catch( InterruptedException e ){ //Producer 58

class Consumer extends Thread{ int id; BufferLimitato<Integer> bl; public Consumer( int id, BufferLimitato<Integer> bl ){ this.id=id; this.bl=bl; public void run(){ while( true ){ int x=((integer)bl.get()).intvalue(); //oppure: int x=bl.get(); //auto-unboxing System.out.println("Consumer#"+id+" got item# "+x); //Consumer public class TB{ public static void main( String []args ){ BufferLimitato<Integer> b=new BufferLimitatoMH<Integer>( 5 ); Producer p1=new Producer( 1, 0, b ); Producer p2=new Producer( 2, 1000000, b ); Consumer c1=new Consumer( 1, b ); Consumer c2=new Consumer( 2, b ); Consumer c3=new Consumer( 3, b ); p1.start(); p2.start(); c1.start(); c2.start(); c3.start(); //TB TB.java è parte del package sde.buffer. Tutto ciò semplifica l accesso alle classi BufferLimitatoS, BufferLimitatoMH, BufferLimitatoJS. Tuttavia per l esecuzione occorre avvertire l interprete di Java che il main è in una classe dello stesso package come segue: >java sde.buffer.tb INVIO ossia specificando per esplicito il package di appartenenza di TB.class. La soluzione proposta rende possibile cambiare l implementazione del buffer modificando nel metodo main l indicazione (al lato destro di BufferLimitato b=new BufferLimitatoXX( 5 )) della classe utilizzata. Nessun altra modifica è richiesta. Barbiere Addormentato Si deve gestire un salone in cui è presente un solo barbiere, una sedia di servizio ed un certo numero di sedie per l attesa dei clienti. Quando non ci sono clienti da servire, il barbiere si addormenta sulla sedia di servizio. Un cliente che arrivi e trovi il barbiere addormentato, lo sveglia e passa immediatamente in servizio. Un cliente che trovi il barbiere in servizio e qualche posto libero, aspetta il suo turno. Se non esistono posti a sedere, il cliente va via non servito. 59

Si mostrano tre soluzioni utilizzando rispettivamente i meccanismi Java standard (SaloneJS), il monitor di Hoare (SaloneMH) e i semafori binari (SaloneSB). Le realizzazioni sono parte del package sde.salone. La classe astratta Salone definisce le entità comuni alle varie implementazioni. Ogni estensione di Salone deve implementare il metodo run() dell interfaccia Runnable dal momento che il thread barbiere è incorporato all interno del salone. L operazione richiesta() è l unica che può essere utilizzata dai clienti. Essa ritorna false se al momento del tentativo il salone è tutto occupato, true se il cliente va via servito. //Salone.java package sde.salone; public abstract class Salone implements Runnable{ protected int sedie, posti; protected Thread barbiere=null; protected boolean dorme, //true se il barbiere dorme cliente; //true se un cliente e' seduto per servizio public Salone( int sedie ){ this.sedie=sedie; posti=sedie; dorme=true; cliente=false; barbiere=new Thread(this); public abstract boolean richiesta(); public abstract void run(); //Salone Una prima estensione di Salone si basa sull uso dei metodi synchronized e delle operazioni wait(), notify(), notifyall() di Java. //SaloneJS.java package sde.salone; public class SaloneJS extends Salone{ protected boolean fineattesa=false; public SaloneJS( int sedie ){ super( sedie ); barbiere.start(); 60

public synchronized boolean richiesta(){ if( posti==0 ) return false; if( posti<sedie!dorme ){ posti--; while( true ){ try{ wait(); catch(interruptedexception e){ if( fineattesa ){ fineattesa=false; cliente=true; //cliente si accomoda su poltrona barbiere notifyall(); break; else{ dorme=false; cliente=true; notify(); //cliente aspetta fine servizio while( cliente ) try{ wait(); catch(interruptedexception e){ return true; public void run(){ while( true ){ synchronized(this){ if(!cliente ){ if( posti==sedie ){ dorme=true; //barbiere dorme per assenza di clienti while( dorme ) try{ wait(); catch(interruptedexception e){ else{ posti++; fineattesa=true; notify(); //barbiere attende cliente per servizio while(!cliente ) try{ wait(); catch(interruptedexception e){ //servizio try{ Thread.sleep( (int)(math.random()*1000) ); catch(interruptedexception e){ synchronized(this){ cliente=false; //cliente si alza per fine servizio notifyall(); //while //run //SaloneJS 61

Si dovrebbe prestare attenzione al modo con cui si risvegliano i clienti dall attesa in sala di aspetto. Esiste la booleana fineattesa che viene posta a true dal barbiere. Il primo cliente che si sveglia (non necessariamente in ordine FIFO) trova la variabile a true, la mette a false e sveglia tutti i rimanenti thread (barbiere incluso). Gli altri clienti tornano a dormire, il barbiere si sveglia dall attesa che il cliente si sia accomodato per servizio. Un altro punto degno di nota è l attesa del cliente sotto servizio. Non potendosi distinguere tra questo thread cliente e gli eventuali altri cliente in attesa, l unica maniera che ha il barbiere per segnalare la fine del servizio è eseguire notifyall() avendo posto cliente=false e mantenendo fineattesa=false. Dunque a svegliarsi sarà solo il thread servito (d altra parte il barbiere è più che mai sveglio in questo momento). Gli altri dettagli dovrebbero essere auto-esplicativi. La seconda soluzione, SaloneMH, è equivalente a Salone JS ed utilizza monitor e condition esplicite. //SaloneMH.java package sde.salone; import sde.monitor.*; public class SaloneMH extends Salone{ private Monitor m=new Monitor(); private Condition attesa=m.newcondition(); private Condition barber=m.new Condition(); private Condition servizio=m.new Condition(); public SaloneMH( int sedie ){ super(sedie); barbiere.start(); public boolean richiesta(){ m.enter(); if( posti==0 ){ m.exit(); return false; if( posti<sedie!dorme ){ posti--; attesa.wait(); cliente=true; //cliente si accomoda su poltrona barbiere else{ dorme=false; cliente=true; barber.signal(); //cliente aspetta fine servizio servizio.wait(); m.exit(); return true; public void run(){ while( true ){ m.enter(); if(!cliente ){ if( posti==sedie ){ 62

dorme=true; barber.wait(); else{ posti++; attesa.signal(); m.exit(); //servizio try{ Thread.sleep( (int)(math.random()*1000) ); catch(interruptedexception e){ m.enter(); cliente=false; servizio.signal(); //fine servizio, cliente si alza m.exit(); //while //run //SaloneMH Si lascia allo studio del lettore la comprensione di tutti i dettagli di SaloneMH nonché una valutazione comparativa delle due soluzioni. Si presenta, infine, un implementazione della classe Salone, SaloneSB, basata sui semafori binari. Essa segue naturalmente dalla soluzione a monitor e condition esplicite. //SaloneSB.java package sde.salone; import sde.semaforo.*; public class SaloneSB extends Salone implements Runnable{ SemaforoBinario mutex=new SemaforoBinario(1); SemaforoBinario attesa=new SemaforoBinario(0); SemaforoBinario barber=new SemaforoBinario(0); SemaforoBinario inizioservizio=new SemaforoBinario(0); SemaforoBinario fineservizio=new SemaforoBinario(0); public SaloneSB( int sedie ){ super(sedie); barbiere.start(); public boolean richiesta(){ mutex.p(); if( posti==0 ){ mutex.v(); return false; if( posti<sedie!dorme ){ posti--; mutex.v(); attesa.p(); cliente=true; //cliente si accomoda su poltrona barbiere 63

mutex.v(); else{ dorme=false; cliente=true; barber.v(); inizioservizio.v(); fineservizio.p();//cliente aspetta fine servizio return true; public void run(){ while( true ){ mutex.p(); if(!cliente ){ if( posti==sedie ){ dorme=true; mutex.v(); barber.p(); mutex.v(); else{ posti++; attesa.v(); else mutex.v(); inizioservizio.p(); //servizio try{ Thread.sleep( (int)(math.random()*1000) ); catch(interruptedexception e){ mutex.p(); cliente=false; fineservizio.v(); //fine servizio, cliente si alza mutex.v(); //while //run //SaloneSB L utilizzo di una qualsiasi realizzazione di Salone necessita della caratterizzazione del cliente (o persona). Quella che segue è una classe appartenente allo stesso package sde.salone. //Persona.java package sde.salone; public class Persona extends Thread{ private Salone s; private int id; public Persona( Salone s, int id ) { this.s=s; this.id=id; public void run(){ if(!s.richiesta() ) System.out.println("Cliente "+"#"+id+" va via non servito"); else System.out.println("Cliente "+"#"+id+" va via servito"); 64

//Persona Come si vede, per generalità, Persona riceve un Salone. Dunque, senza alcuna modifica essa funziona trasparentemente con tutte e tre le implementazioni di Salone. I clienti arrivano al salone attraverso la porta implementata direttamente nel metodo main di un applicazione. Ciò che serve è un semplice meccanismo di generazione. //TSAL.java import sde.salone.*; public class TSAL{ public static void main(string []args){ Salone s=new SaloneXX( 5 ); int i=0; for(;;){ try{ Thread.sleep( (int)(math.random()*500) ); catch(interruptedexception e){ Persona c=new Persona(s,i); i++; c.start(); //TSAL La modifica da attuare per utilizzare l una o l altra implementazione del salone riguarda solo l uso del costruttore della classe a destra dell assegnazione: Salone s=new SaloneXX( 5 ); //XX può essere JS, MH e SB Canali sincroni CSP Si presentano due implementazioni del concetto di canale sincrono à la CSP. //file Channel.java package sde.csp; public abstract class Channel{ protected boolean senderok=false, receiverok=false; protected Object message; protected boolean senderconnected=false, receiverconnected=false; public abstract OutPort senderconnection(); public abstract InPort receiverconnection(); //Channel //file InPort.java package sde.csp; 65

public interface InPort{ public Object receive(); //InPort //file OutPort.java package sde.csp; public interface OutPort{ public void send( Object msg ); //OutPort A) Implementazione Java standard //file ChannelJS.java package sde.csp; public class ChannelJS extends Channel{ private InputPort ip=new InputPort(); private OutputPort op=new OutputPort(); private class InputPort implements InPort{ public Object receive(){ synchronized( ChannelJS.this ){ if(!senderconnected!receiverconnected ) throw new RuntimeException(); Object msg; receiverok=true; while(!senderok ) try{ ChannelJS.this.wait(); catch(interruptedexception e){ msg=message; senderok=false; receiverok=false; ChannelJS.this.notify(); return msg; //receive //InputPort private class OutputPort implements OutPort{ public void send( Object msg ){ synchronized( ChannelJS.this ){ if(!senderconnected!receiverconnected ) throw new RuntimeException(); message=msg; senderok=true; if( receiverok ) ChannelJS.this.notify(); while( senderok ) try{ ChannelJS.this.wait(); catch(interruptedexception e){ 66

//OutputPort public synchronized OutPort senderconnection(){ if( senderconnected ) throw new RuntimeException( Sender already connected ); senderconnected=true; return op; public synchronized InPort receiverconnection(){ if( receiverconnected ) throw new RuntimeException( Receiver already connected! ); receiverconnected=true; return ip; //ChannelJS La soluzione proposta si caratterizza per il tentativo di esportare interfacce diverse al sender ed al receiver impegnati su uno stesso canale. Il sender deve poter eseguire solo send. Il receiver solo receive. Per ottenere questo risultato, sono state introdotte due interfacce InPort ed OutPort e due inner class di Channel InputPort e OutputPort che implementano l omonima interfaccia. Di ciascuna inner class viene creato un singolo oggetto al tempo di costruzione del canale. E richiesto che sender e receiver si connettano preliminarmente all oggetto canale con la relativa intenzione. L operazione di connessione ritorna l interfaccia d uso del canale, ossia la visione come porta di uscita (per il sender) e porta di ingresso (per il receiver). A questo punto, ogni processo è vincolato dal protocollo stabilito dall interfaccia ottenuta. Per la corretta sincronizzazione del sender e receiver, si utilizza, nei metodi send/receive, il blocco sincronizzato in cui l oggetto provvisto di monitor è il canale. Tutto ciò si ottiene con la sintassi synchronized( ChannelJS.this ){... che chiarisce che this non si riferisce all oggetto della inner class ma all oggetto canale che lo ospita. Avrebbe poco senso utilizzare, essendo distinte le inner class InputPort e OutputPort, il pronome this (perchè?). Si osserva infine che l implementazione del rendezvous lascia l ultima parola al receiver. Ossia, è il receiver, che appena ottiene il messaggio, sblocca il sender ed entrambi riprendono ad eseguire in concorrenza. B) Implementazione mediante monitor di Hoare //file ChannelMH.java 67

package sde.csp; import sde.monitor.*; public class ChannelMH extends Channel{ private Monitor m=new Monitor(); private Condition csender=m.newcondition(); private Condition creceiver=m.newcondition(); private OutputPort op=new OutputPort(); private InputPort ip=new InputPort(); private class OutputPort implements OutPort{ public void send( Object msg ){ m.enter(); if(!receiverconnected ){ m.exit(); throw new RuntimeException( Receiver not connected ); message=msg; senderok=true; if(!receiverok ) csender.wait(); else creceiver.signal(); m.exit(); //OutputPort private class InputPort implements InPort{ public Object receive(){ m.enter(); if(!senderconnected ){ m.exit(); throw new RuntimeException( Sender not connected ); Object msg; receiverok=true; if(!senderok ){ creceiver.wait(); msg=message; senderok=false; receiverok=false; else{ msg=message; senderok=false; receiverok=false; csender.signal(); m.exit(); return msg; //InPort public OutPort senderconnection(){ m.enter(); if( senderconnected ){ m.exit(); throw new RuntimeException( Sender already connected! ); 68

senderconnected=true; m.exit(); return op; public InPort receiverconnection(){ m.enter(); if( receiverconnected ){ m.exit(); throw new RuntimeException( Receiver already connected! ); receiverconnected=true; m.exit(); return ip; //ChannelMH Un applicazione: Crivello di Eratostene parallelo Si mostra un programma che utilizza il concetto di canale sincrono nella ricerca dei numeri primi tra 2 ed un intero positivo assegnato N col metodo del crivello di Eratostene. L applicazione si basa su un thread generatore che provvede a generare in sequenza gli interi da 2 ad N, e su una pipeline di thread filtri, che di momento in momento riflette l insieme dei numeri primi conosciuti. Ogni filtro è depositario di un numero primo. Il suo compito è filtrare gli interi ricevuti in input. Un numero multiplo del proprio primo è sicuramente non primo e si può abbandonare (non trasmettere oltre nella pipeline). Un intero non multiplo del proprio primo va comunicato al prossimo filtro a valle. Se un tale filtro non esiste, allora si è in presenza di un nuovo primo. Si genera un nuovo filtro, lo si lega al primo individuato e si connette il filtro come ultimo stadio della pipeline. I collegamenti tra il generatore ed il primo filtro (associato al numero 2) e tra i diversi filtri della pipeline sono realizzati mediante canali sincroni. Pertanto, ad es., il generatore non provvede a produrre un nuovo intero se non ha consegnato (rendezvous) il precedente al primo filtro etc. A fine elaborazione, il generatore invia il tappo 0 al primo filtro. Il numero zero serve a richiedere in ordine ai filtri di emettere in uscita il proprio numero. Gli altri dettagli dovrebbero essere auto-esplicativi. //file Crivello.java package sde.csp; import sde.inout.*; class Generatore extends Thread{ int n; OutPort outport; public Generatore( int n ){ 69

this.n=n; Channel c=new ChannelJS(); outport=c.senderconnection(); Filtro f=new Filtro( 2, c.receiverconnection() ); public void run(){ for( int i=3; i<=n; i++ ){ outport.send( new Integer( i ) ); try{ sleep( (int)(math.random()*10) ); catch(interruptedexception e){ outport.send( new Integer(0) ); //Generatore class Filtro extends Thread{ static int conta=0; int primo; InPort inport; OutPort outport=null; public Filtro( int primo, InPort inport ){ this.primo=primo; this.inport=inport; start(); public void run(){ while( true ){ int x=((integer)inport.receive()).intvalue(); if( x==0 ){ Format.print( System.out, "%6d", primo ); conta++; if( conta%12==0 ) System.out.println(); if( outport!=null ) outport.send( new Integer( x ) ); break; if( x%primo!= 0 ){ if( outport==null ){ Channel c=new ChannelJS(); outport=c.senderconnection(); Filtro f=new Filtro( x, c.receiverconnection() ); else outport.send( new Integer( x ) ); //Filtro public class Crivello{ public static void main( String []args ){ System.out.println("Crivello di Eratostene Parallelo"); System.out.println("Trova i numeri primi tra 2 e N"); int n=console.readint("n="); Generatore g=new Generatore( n ); 70

g.start(); //Crivello Esercizi 1. (MergeSort parallelo) Si ha un vettore v di interi da ordinare col metodo MergeSort. Si divide v in due parti uguali e si attiva ricorsivamenteparallelamente un differente thread per ordinare ciascuna parte separatamente. Ad ogni thread, diciamolo Sorter, si passa v e due indici che individuano la porzione da ordinare. Finito l ordinamento delle due parti, il thread padre fa il merge delle due porzioni ordinate e termina a sua volta. Realizzare l attesa di un thread genitore dei due thread figli con l'operazione join. 2. (LettoriScrittori) Si progetti utilizzando il monitor nativo di Java la classe LettoriScrittori in modo da mantenere priorità ai lettori e assenza di starvation per gli scrittori. 3. (Ponte a senso unico alternato) Lungo una strada a due corsie e doppio senso di marcia, che procede in direzione nord-sud, vi è un ponte ad una sola corsia, percorribile a senso unico alternato. Il ponte ha la capacità di n automobili. Le automobili dirette verso nord (oppure verso sud) possono attraversare il ponte solo se non è attraversato da nessuna automobile diretta verso sud (oppure verso nord). Programmare in Java i processi automobile diretta verso nord e automobile diretta verso sud utilizzando prima il meccanismo dei semafori e poi una struttura a monitor. 4. (Il Problema dei fumatori di sigarette) Esistono tre fumatori ed un tabaccaio. Ciascun fumatore prepara continuamente una sigaretta e la fuma, ma per preparare e fumare la sigaretta il fumatore deve ovviamente disporre dei tre ingredienti fondamentali: carta, tabacco e fiammiferi. Un fumatore ha la carta, l altro il tabacco ed il terzo i fiammiferi. Il tabaccaio dispone dei tre ingredienti in quantità infinita. Il tabaccaio posa sul tavolo due ingredienti per volta. Il fumatore che dispone del terzo ingrediente mancante può preparare una sigaretta e fumarla, segnalando al tabaccaio quando ha terminato. Il tabaccaio, allora, mette altri due ingredienti sul tavolo e la storia si ripete. Si scriva un programma concorrente Java per sincronizzare tabaccaio e fumatori. 5. Si ha un sistema con n processi P1, P2,..., Pn, ciascuno dei quali è dotato di un numero di priorità unico. Si scriva un monitor che allochi a questi tre processi tre stampanti identiche, utilizzando i numeri di priorità per stabilire l ordine di allocazione. 71

6. Un file deve essere condiviso tra diversi processi, ciascuno dei quali ha un numero identificativo unico (pid). Al file possono accedere contemporaneamente più processi ma col vincolo che la somma di tutti i pid dei processi che accedono concorrentemente al file deve essere minore o uguale di una soglia S. Si scriva un programma basato sui monitor di Java per coordinare l accesso al file. La soluzione deve garantire che l ordine dei risvegli sia rigorosamente FIFO. 7. (Rendezvous) Si progetti una classe ChannelSB erede della classe Channel di cui al caso di studio sui canali sincroni, basata sull utilizzo dei semafori binari. Come test della nuova classe, si utilizzi il crivello di Eratostene. 8. L incrocio tra una strada di grande traffico e una strada pedonale è regolato da un semaforo. Il semaforo è normalmente verde per i veicoli che procedono nei due sensi lungo la strada di grande comunicazione. I pedoni che intendono attraversare possono ottenere il colore verde solo premendo un apposito pulsante. Programmare la gestione del semaforo in modo tale che entrambi, i veicoli e i pedoni, prima di procedere devono attendere una risposta esplicita dal gestore del semaforo. La politica del gestore è quella di accogliere le richieste dei pedoni solo quando non ha richieste pendenti da parte dei veicoli. 9. Una linea ferroviaria disposta in direzione nord-sud collega n stazioni, che la suddividono in n-1 tronchi, ed è percorsa da t treni. Le stazioni hanno indici 1..n, crescenti in senso nord-sud; il tronco compreso tra le stazioni i e i+1 ha indice i. Tutti i tronchi sono a binario unico. Per il transito o la sosta dei treni, la stazione i dispone di Bsudi binari riservati ai treni che viaggiano verso sud e Bnordi binari riservati ai treni che viaggiano verso nord. I treni sono processi permanenti che percorrono alternativamente la linea ferroviaria in direzione sud e nord; la stazione e il verso iniziale sono definiti tempo della loro creazione. Le risorse utilizzate dai treni sono i tronchi della ferrovia e i binari delle stazioni. Ad ogni treno è associata una priorità e le risorse sono assegnate in base alla priorità dei treni. Scrivere un programma che risolva il problema e valutare la possibilità di deadlock. 10. (Il problema dei tre barbieri) Costituisce una estensione del problema del barbiere addormentato presentato come caso di studio. In questa nuova situazione, si hanno tre barbieri e tre sedie di servizio, un sofà con 4 posti a sedere, un area presso la quale possono sostare al massimo 16 persone in piedi, una cassa presso cui un cliente servito paga prima di andarsene. Quando entra un cliente, si danno le seguenti possibilità: 72

se tutti i posti del sofà sono occupati cosi come la zona di attesa in piedi (20 persone sono già in attesa) il cliente se ne va se i barbieri sono occupati ed esistono posti disponibili sul sofà o, in mancanza, in piedi, il cliente attende se qualche barbiere sta dormendo, il primo cliente che entra lo sveglia per essere servito Prima di andare via, un cliente deve pagare alla cassa. Tuttavia, non esiste un cassiere. Un barbiere, non appena termina un servizio, può assumere temporaneamente le funzioni di cassiere e smaltire i clienti in coda per il pagamento. Quindi ritorna a servire clienti. Il sofà, l area di attesa in piedi e la lista dei clienti davanti alla cassa sono gestiti in forma FIFO. Non appena termina un servizio, il cliente che da più tempo aspetta sul sofà guadagna la sedia di un barbiere. Il posto libero sul sofà viene quindi occupato dal cliente che aspetta da più tempo nella zona in piedi etc. Progettare una soluzione del problema utilizzando separatamente il monitor di Java, il monitor di Hoare, i semafori. Occorre garantire l assenza di situazioni spiacevoli come: clienti che si affollano intorno alle sedie dei barbieri ostacolandone l attività; un cliente che siede su una poltrona di servizio sulle gambe di un cliente ancora in servizio; etc. 73