Reti e problematiche di Rete I Processi Concorrenti
Enrico Cavalli
Anno Accademico 2008-2009
I problemi con i processi concorrenti
1
I processi concorrenti •
La concorrenza tra processi avviene secondo diverse modalità. Si parla infatti di competizione nell’accesso alle risorse come nel caso di due amici che litigano per la restituzione di un prestito e di cooperazione nell’evoluzione dei processi come nel caso della produzione dei cedolini paga degli stipendi eseguiti da due processi, uno dei quali effettua il calcolo della paga e l’altro la stampa
•
La cooperazione tra processi può avvenire con la condivisione di risorse, per esempio mediante la condivisione di un’area della memoria centrale per processi concorrenti su un singolo calcolatore, oppure tramite comunicazione, con lo scambio di messaggi, come avviene ai processi che interagiscono in un sistema distribuito.
•
La competizione nell’accesso alle risorse può portare allo stallo dei processi mentre la cooperazione tra processi concorrenti può causare l’inconsistenza dei dati elaborati.
•
I problemi di concorrenza tra processi si presentano sia con i processi di sistema che con i processi degli utenti. La concorrenza è importante sia per la comprensione dei sistemi operativi sia nello sviluppo di applicazioni con linguaggi che supportano il multi threading.
3
Reti e problematiche di rete - Enrico Cavalli - Università di Bergamo
I processi concorrenti P0
P1 Estratto Conto
Dato
Dati Clienti
Buffer
La produzione degli estratti conto con due processi cooperanti
/* ------------------------------------------------------------------* P0 esegue P1 esegue * ------------------------------------------------------------------- */ do do ProduceDatiCliente; LeggeDatiDaBuffer; ScriveDatiNelBuffer; StampaEstrattoConto; while (EsistonoClienti); while (EsistonoClienti);
do ProduceDatiCliente; ScriveDatiNelBuffer; LeggeDatiDaBuffer; StampaEstrattoConto; while (EsistonoClienti);
eseguito da P0 eseguito da P0 eseguito da P1 eseguito da P1
Reti e problematiche di rete - Enrico Cavalli - Università di Bergamo
Il comportamento desiderato
4
2
I processi concorrenti L’elaborazione concorrente può portare a malfunzionamenti per: accesso concorrente alle informazioni e differente velocità di esecuzione dei processi
NumeroConto Saldo
Dati 0 Cliente B 5784 215.32
Buffer Cliente A 5783 3225.15
Dati1 Cliente A 5783 3225.15
typedef struct { int NumeroConto; double Saldo; } contoCorrente; contoCorrente Dati0, Dati1, Buffer; /*----------------------------------------------------------------* Per effetto dell’accesso concorrente può succedere che *----------------------------------------------------------------- */ Buffer.NumeroConto = Dati0.NumeroConto; eseguito da P0 Dati1.NumeroConto = Buffer.NumeroConto; eseguito da P1 Dati1.Saldo = Buffer.Saldo; eseguito da P1 Buffer.Saldo = Dati0.Saldo; eseguito da P0
NumeroConto Saldo
Dati 0 Cliente B 5784 215.32
Buffer Cliente B 5784
Dati1 Cliente ? 5784
215.32
3225.15
Reti e problematiche di rete - Enrico Cavalli - Università di Bergamo
5
I processi concorrenti
La differente velocità di esecuzione dei processi può portare alla perdita di informazioni o alla duplicazione delle informazioni.
•
Se P0 è più veloce di P1 può succedere che P0 pone nel buffer i dati di C prima che P1 ha letto quelli di B, che non saranno mai stampati.
•
Se P1 è più veloce di P0, può accadere che P1, dopo avere stampato i dati di A, acceda al buffer prima che P0 lo abbia aggiornato con i dati di B. In tale caso P1 stamperebbe i dati di A due volte.
I due problemi principali della programmazione concorrente sono: - Accesso concorrente alle informazioni - Sincronizzazione dei processi
Reti e problematiche di rete - Enrico Cavalli - Università di Bergamo
6
3
I processi concorrenti Per risolvere il problema dell’accesso concorrente alle informazioni bisogna impedire che mentre P0 modifica il buffer, P1 lo acceda per leggerlo. In pratica questo significa che le due procedure ScriveDatiNelBuffer, LeggeDatiDaBuffer devono essere eseguite in mutua esclusione: Le porzioni di codice P0, P1, .. , Pn, sono eseguite in mutua esclusione se l’esecuzione di Pi può iniziare solo se nessuna Pj, i ≠ j, è in esecuzione.
Le attività dei due processi devono anche essere sincronizzate per evitare che un estratto conto non sia stampato o che sia stampato più volte: • P0 può scrivere nel buffer solo se il buffer è vuoto. In pratica: P0 può scrivere nel buffer solo dopo che P1 lo ha letto svuotandolo. • P1 può leggere il buffer solo se il buffer è pieno. In pratica: P1 può leggere il buffer solamente dopo che P0 ha scritto nel buffer riempiendolo.
Reti e problematiche di rete - Enrico Cavalli - Università di Bergamo
7
I processi concorrenti /* ---------------------------------------------------------------------* Processo P0 Processo P1 * ---------------------------------------------------------------------- */ do do ProduceDatiCliente; AttendiBufferPieno; AttendiBufferVuoto; LeggeDatiDaBuffer; ScriveDatiNelBuffer; BufferVuoto; BufferPieno; StampaEstrattoConto; while(EsistonoClienti); while(EsistonoClienti); /* ------------------------------------------------------------* Programma EstrattoConto * ------------------------------------------------------------- */ void main() begin typedef enum {vuoto,pieno} stato; stato StatoBuffer; contoCorrente Buffer; parbegin P0(); P1(); parend; void P0(); void P1(); P0 e P1 sono eseguite in parallelo StatoBuffer = vuoto; parbegin P0(); P1(); parend; end;
Reti e problematiche di rete - Enrico Cavalli - Università di Bergamo
8
4
I processi concorrenti /* -----------------------------------------------------------------------* Processo P0 * ------------------------------------------------------------------------ */ void P0() begin do ProduceDatiCliente; while (StatoBuffer == pieno) endwhile; attende: buffer == vuoto ScriveDatiNelBuffer; StatoBuffer = pieno; buffer vale pieno while (EsistonoClienti); end; /* ----------------------------------------------------------------------Soluzione ottenuta con: * Processo P1 - Attesa attiva * ----------------------------------------------------------------------*/ - Processi forzati alla stessa velocità void P1() begin do while (StatoBuffer == vuoto) endwhile; attende: buffer == pieno LeggeDatiDaBuffer; StatoBuffer = vuoto; buffer vale vuoto StampaEstrattoConto; while (EsistonoClienti); end;
9
Reti e problematiche di rete - Enrico Cavalli - Università di Bergamo
Accesso alle regioni critiche /* ---------------------------------------------------------------------* Conteggio dei posti liberi * ---------------------------------------------------------------------- */ Posti = Posti - 1;
LOAD R1,Posti SUB R1,UNO STORE R1,Posti
Esecuzione concorrente di due processi che eseguono: Posti = Posti - 1;
P0: Posti-50 P1: Posti--
Istruzioni
Processo
. . . LOAD . . . LOAD SUB STORE . . . SUB STORE . . .
50 P0 50 50 -- schedulato P1, salvato R1(50) 50 50 P1 P1 49 50 P1 49 49 –- riprende P0, ripristinato R1(50) 49 49 P0 P0 49 49
R1,posti R1,posti R1,uno R1,posti R1,uno R1,Posti
Reti e problematiche di rete - Enrico Cavalli - Università di Bergamo
R1
Posti
10
5
Corse critiche Per risolvere il problema delle corse critiche servono due istruzioni speciali: EntraSezioneCritica;
EsciSezioneCritica;
da inserire immediatamente prima e dopo la sezione critica che ne garantiscono l’esecuzione in mutua esclusione.
/* -------------------------------------------------------------------* Processo P0 Processo P1 * -------------------------------------------------------------------- */ do do ArrivaSpettatore; ArrivaSpettatore; EntraSezioneCritica; EntraSezioneCritica; Posti = Posti - 1; Posti = Posti - 1; EsciSezioneCritica; EsciSezioneCritica; forever; forever;
Reti e problematiche di rete - Enrico Cavalli - Università di Bergamo
11
Corse critiche /* ------------------------------------------------------------------------* Programma ContaPostiLiberi * ------------------------------------------------------------------------- */ void main() begin int Posti; Soluzione corretta typedef enum {P0, P1} turno; ma non accettabile turno Processo; void P0(); void P1(); operativamente Posti = 400; perché . . . Processo = P0; parbegin P0(); P1(); parend; end; /* ------------------------------------------------------------------------* Processo P0 Processo P1 * ------------------------------------------------------------------------- */ void P0() void P1() begin begin do do ArrivaSpettatore; ArrivaSpettatore; while(Processo==P1) endwhile; while(Processo==P0) endwhile; Posti = Posti - 1; Posti = Posti - 1; Processo = P1; Processo = P0; forever; forever; end; end;
Reti e problematiche di rete - Enrico Cavalli - Università di Bergamo
12
6
La mutua esclusione in software
La mutua esclusione in software Una buona soluzione al problema della mutua esclusione richiede che siano rispettate certe condizioni: - la mutua esclusione fra i processi deve essere garantita; - un processo fuori dalla sezione critica non deve impedire né ritardare l’accesso alla sezione critica da parte degli altri processi che vi vogliono accedere; - non si possono fare ipotesi sulla velocità relativa dei diversi processi; - i processi rimangono nella loro sezione critica per un tempo limitato; - non ci deve essere stallo nè ritardo indefinito. Consideriamo diversi approcci alla soluzione del problema della mutua esclusione
Reti e problematiche di rete - Enrico Cavalli - Università di Bergamo
14
7
Mutua esclusione in software /* ----------------------------------------------------------------------- * * Programma MutuaEsclusione1 * * ----------------------------------------------------------------------- */ void main() La mutua begin esclusione non è boolean Occupata; void P0(); void P1(); garantita Occupata = false; parbegin P0(); P1(); parend; end; // -----------------------------------------------------------------------void P0() void P1() begin begin . . . . . . while (Occupata) endwhile; while (Occupata) endwhile; Occupata = true; Occupata = true; Occupata = false; Occupata = false; . . . . . . end; end;
Reti e problematiche di rete - Enrico Cavalli - Università di Bergamo
15
Mutua esclusione in software /* --------------------------------------------------------------------- * * Programma MutuaEsclusione2 * * --------------------------------------------------------------------- */ void main() begin boolean VoglioEntrare[2]; C’è pericolo di void P0(); void P1(); stallo VoglioEntrare[0] = VoglioEntrare[1] = false; parbegin P0(); P1(); parend; end; //-----------------------------------------------------------------------void P0() void P1() begin begin . . . . . . VoglioEntrare[0] = true; VoglioEntrare[1] = true; while (VoglioEntrare[1]) while (VoglioEntrare[0]) endwhile; endwhile; VoglioEntrare[0] = false; VoglioEntrare[1] = false; . . . . . . end; end;
Reti e problematiche di rete - Enrico Cavalli - Università di Bergamo
16
8
Mutua esclusione in software /* --------------------------------------------------------------------- * * Programma MutuaEsclusione3 * * --------------------------------------------------------------------- */ void main() begin boolean VoglioEntrare[2]; void P0(); void P1(); C’è pericolo di VoglioEntrare[0] = VoglioEntrare[1] = false; ritardo indefinito parbegin P0(); P1(); parend; end; //-----------------------------------------------------------------------void P0() void P1() begin begin . . . . . . VoglioEntrare[0] = true; VoglioEntrare[1] = true; while (VoglioEntrare[1]) while (VoglioEntrare[0]) VoglioEntrare[0]=false; VoglioEntrare[1]=false; AspettaCasuale(); AspettaCasuale(); VoglioEntrare[0]=true; VoglioEntrare[1]=true; endwhile; endwhile; VoglioEntrare[0] = false; VoglioEntrare[1] = false; . . . . . . end; end; Reti e problematiche di rete - Enrico Cavalli - Università di Bergamo
17
Mutua esclusione in software Nel 1965 l’olandese Dekker ha proposto un algoritmo per risolvere via software il problema della mutua esclusione. L’algoritmo di Dekker è piuttosto complicato e difficile da comprendere. Solo nel 1981 Peterson ha proposto una nuova soluzione molto più semplice di quella proposta da Dekker. Il fatto che ci siano voluti ben 16 anni per scoprire un nuovo semplice algoritmo per risolvere via software il problema della mutua esclusione lascia comprendere come siano difficili da risolvere i problemi che si incontrano con i processi concorrenti. L’algoritmo di Peterson, che combina le idee di ContaPostiLiberi e MutuaEsclusione2 è, per così dire, un algoritmo basato sulla gentilezza perché, in caso di accesso contemporaneo alla sezione critica, entrambi i processi cedono il passo all’altro. Si noti che anche l’algoritmo di Peterson, semplice ed elegante, fa uso del ciclo di attesa attiva e, di conseguenza, non è molto efficiente.
Reti e problematiche di rete - Enrico Cavalli - Università di Bergamo
18
9
Mutua esclusione in software /* ---------------------------------------------------------------------- * * AlgoritmoDiPeterson * * ---------------------------------------------------------------------- */ void main() begin boolean VoglioEntrare[2]; Se P0 è nella int Turno; propria sezione void P0(); void P1(); critica allora non VoglioEntrare[0] = VoglioEntrare[1] = false; può esserlo P1 Turno = 1; parbegin P0(); P1(); parend; end; //-----------------------------------------------------------------------void P0() void P1() begin begin . . . . . . VoglioEntrare[0] = true; VoglioEntrare[1] = true; Turno = 1; Turno = 0; while (VoglioEntrare[1] AND while (VoglioEntrare[0] AND Turno==1) endwhile; Turno==0) endwhile; VoglioEntrare[0] = false; VoglioEntrare[1] = false; . . . . . . end; end; Reti e problematiche di rete - Enrico Cavalli - Università di Bergamo
19
Supporti Hardware per la programmazione concorrente
10
Supporti hardware La ragione del fallimento della mutua esclusione nel programma MutuaEsclusione1 è dovuto al fatto che le coppia di istruzioni: while (Occupata) endwhile; Occupata = true; non è eseguita in modo atomico. Se fosse possibile eseguire quella coppia di istruzione in modo atomico MutuaEsclusione1 funzionerebbe correttamente. Ci sono due modi per eseguire le due istruzioni atomicamente: - disabilitare le interruzioni prima di eseguire le istruzioni che devono essere eseguite in modo atomico e riabilitarle subito dopo. La tecnica della disabilitazione delle interruzioni è fortemente sconsigliata per tre ragioni: è pericoloso autorizzare un processo a disabilitare le interruzioni; il calcolatore funziona in modo inefficiente perché il sistema operativo non può attuare le proprie politiche di ottimizzazione; non funziona nei sistemi multi processore (e questo taglia la testa al toro) - usare istruzioni macchina (se ci sono) che controllano il valore di un dato e lo modificano nella medesima istruzione
21
Reti e problematiche di rete - Enrico Cavalli - Università di Bergamo
L’istruzione TSL In molti processori è presente l’istruzione macchina TSL, acronimo di Test and Set Lock: TSL Registro, Blocco che trasferisce in un registro il contenuto della parola di memoria Blocco e, nella medesima istruzione, memorizza in Blocco un valore intero diverso da zero: il comportamento di TSL può essere descritto come quello della funzione TestSet che: restituisce il valore del proprio argomento X (1) e assegna a X il valore true (2) TestSet(X): true
2
1
X
X /* ---------------------------------------------------------------- * * TestSet * * ---------------------------------------------------------------- */ boolean TestSet(boolean &X) begin Boolean SalvaX; SalvaX = X; X = true; return SalvaX; end; Reti e problematiche di rete - Enrico Cavalli - Università di Bergamo
22
11
Mutua esclusione con TSL /* ----------------------------------------------------------------------- * * MutuaEsclusioneTestSet * * ----------------------------------------------------------------------- */ void main() begin boolean Blocco; Blocco = false; parbegin P0(); P1(); parend; end; //-----------------------------------------------------------------------void P0() void P1() begin begin . . . . . . while (TestSet(Blocco)) endwhile; while (TestSet(Blocco)) endwhile; Blocco = false; Blocco = false; . . . . . . end; end;
La soluzione è generalizzabile a N processi concorrenti e opera correttamente sia in ambiente mono che multi processore. Purtroppo usa un ciclo di attesa attiva: sarebbe meglio sospendere un processo e poi svegliarlo
Reti e problematiche di rete - Enrico Cavalli - Università di Bergamo
23
Attesa / risveglio: I Semafori
12
I semafori I semafori sono variabili pensate per gestire i meccanismi di collaborazione tra processi concorrenti. Un processo può fermarsi in attesa di ricevere un segnale inviato da un altro processo. Per inviare e ricevere segnali un processo dispone delle due primitive Wait e Signal: Signal: invia un segnale a un semaforo che lo riceve e lo memorizza Wait: ricevere i segnali inviati a un semaforo: se il segnale non è stato inviato il processo è sospeso in attesa della trasmissione; se invece il segnale è già stato trasmesso il processo lo riceve e prosegue nella esecuzione. I semafori possono essere pensati come oggetti composti da una variabile intera e una coda di processi. Sono creati assegnando un valore opportuno alla variabile intera e possono essere manipolati con le sole primitive Wait e Signal che non sono interrompibili. typedef struct {int Count; CodaDiProcessi Coda;} semaforo; semaforo S (=1); S.Count=1 S.Coda è vuota La variabile intera S.Count conta i segnali inviati al semaforo S; la coda S.Coda è inizialmente vuota
25
Reti e problematiche di rete - Enrico Cavalli - Università di Bergamo
I semafori S.Count = -3 Tre processi in S.Coda
-3
S P2
P1
P0
Una Wait eseguita da un processo P sul semaforo S decrementa S.Count e, se il valore decrementato è minore di 0, blocca P e lo inserisce nella coda associata al semaforo S. L’esecuzione di Signal su un semaforo S incrementa S.Count e, se S.Coda non è vuota, sveglia uno dei processi in attesa nella coda associata a S.
Si dice che Wait consuma i segnali inviati a un semaforo, mentre Signal invia segnali a un semaforo. Count tiene conto del numero di segnali inviati a un semaforo e del loro utilizzo Reti e problematiche di rete - Enrico Cavalli - Università di Bergamo
26
13
I semafori /* ---------------------------------------------------------------------- * * Wait * * ---------------------------------------------------------------------- */ void Wait(semaforo &S) Usa un segnale inviato begin al semaforo S.Count = S.Count - 1; if (S.Count < 0 ) InserisciProcesso(S.Coda); Se non ci sono segnali da usare, il BloccaProcesso; processo P che ha attivato la Wait è fermato e inserito in S.Coda endif; end; /* ---------------------------------------------------------------------- * * Signal * * ---------------------------------------------------------------------- */ void Signal(semaforo &S) begin processo P; Se la coda non è vuota, attiva if (S.Count < 0) un processo P = EstraiProcesso(S.Coda); AttivaProcesso(P); endif; Conta i segnali inviati al S.Count = S.Count + 1; semaforo end; 27
Reti e problematiche di rete - Enrico Cavalli - Università di Bergamo
I semafori P: Wait(S)
P: Wait(S) 0
1
-1 P
Q: Signal(S)
Q: Signal(S)
La figura mostra che la variabile Count di un semaforo S: Per Count ≥ 0 indica il numero di segnali di sveglia che sono stati inviati a S con Signal(S) e non sono stati ancora ricevuti con Wait(S); Quando Count < 0 indica quanti sono i processi nella coda di S, in attesa di ricevere un segnale di sveglia. Con i semafori si risolvono i problemi di mutua esclusione e sincronizzazione Reti e problematiche di rete - Enrico Cavalli - Università di Bergamo
28
14
Mutua esclusione con i semafori L’accesso in mutua esclusione a una porzione di codice si ottiene usando un semaforo S inizializato a 1 e facendo precedere la regione critica con Wait(S) e segnalando l’uscita dalla regione critica con Signal(S) /* ----------------------------------------------------------------- * * MutuaEsclusione con i semafori * * ----------------------------------------------------------------- */ void main() begin Mutua Esclusione: il semaforo Mutex(Count=1); semaforo è inizializzato a 1 void P0(); void P1(); parbegin P0(); P1(); parend; end; /* ----------------------------------------------------------------- */ void P0() void P1() begin begin do do . . . . . . EntraCritica Wait (Mutex) Wait(Mutex); Wait(Mutex); SezioneCritica; SezioneCritica; EsciCritica Signal (Mutex) Signal(Mutex); Signal(Mutex); . . . . . . forever; forever; end; end; 29
Reti e problematiche di rete - Enrico Cavalli - Università di Bergamo
Mutua esclusione con i semafori Se P0 è il primo processo che riesce a eseguire Wait(Mutex) . . .
1
P0 Wait(Mutex)
0
Se anche P1 cerca di accedere alla sezione critica . . .
0
P1 Wait(Mutex)
-1
Quando P0 lascia la sezione critica P1 è svegliato e prosegue . . .
-1
P1
Reti e problematiche di rete - Enrico Cavalli - Università di Bergamo
P0 Signal(Mutex)
0
P1
30
15
Sincronizzazione con i semafori Per sincronizzare due processi su un evento si usa un semaforo S inizializzato a 0. Il processo che rileva l’evento esegue Signal(S) e l’altro processo si sincronizza sull’evento con Wait(S).
/* ----------------------------------------------------------------- * * Sincronizzazione con i semafori * * ----------------------------------------------------------------- */ void main() begin Sincronizzazione: il semaforo è semaforo S(Count=0); inizializzato a 0 void P0(); void P1(); S.Count= 0; parbegin P0(); P1(); parend; end; /* ----------------------------------------------------------------- */ void P0() void P1() begin begin Wait: per sincronizzarsi AzioniPreEvento; RilevaEvento; sull’evento Wait(S); Signal(S); Signal: per rilevare AzioniDopoEvento; l’evento AltreOperazioni; end; end;
31
Reti e problematiche di rete - Enrico Cavalli - Università di Bergamo
Implementazione dei semafori /* ----------------------------------------------------------------- * * Implementazione di Wait(S) * * ----------------------------------------------------------------- */ boolean Occupata void Wait(semaforo &S) begin while (TestSet(Occupata)) endwhile; S.Count = S.Count – 1; if (S.Count < 0) BloccaProcesso BloccaProcesso; Realizzata richiedendo i servizi del InserisciProcesso(S.Coda); sistema operativo per operare il endif; cambiamento di stato del processo Occupata = false; end; /* ----------------------------------------------------------------- * * Implementazione di Signal(S) * * ----------------------------------------------------------------- */ void Signal(semaforo &S) begin AttivaProcesso while (TestSet(Occupata)) endwhile; Realizzata richiedendo i servizi del if (S.Count < 0) sistema operativo per operare il P = EstraiProcesso(S.Coda); cambiamento di stato del processo AttivaProcesso(P); endif; S.Count = S.Count + 1; Realizzate nel Occupata = false; end; nucleo. In
alternativa . . . Reti e problematiche di rete - Enrico Cavalli - Università di Bergamo
32
16
Problemi caratteristici con i semafori
Il problema del produttore consumatore Produttore
Dati
Consumatore
Buffer
il produttore deve essere sicuro di riempire un buffer vuoto; il consumatore deve essere sicuro di svuotare un buffer pieno.
/* ----------------------------------------------------------------- * * Programma ProduttoreConsumatore * * ----------------------------------------------------------------- */ void main() begin semaforo Prelevato(Count=1); semaforo Depositato(Count=0);
Count = 1: Via libera al produttore Count = 0: Blocca il consumatore
string Buffer; void Produttore(); void Consumatore(); parbegin Produttore; Consumatore; parend; end; Reti e problematiche di rete - Enrico Cavalli - Università di Bergamo
34
17
Il problema del produttore consumatore /* * * * * *
----------------------------------------------------------------Produttore: si sincronizza sull’evento Prelevato e rileva l’evento Depositato Consumatore: si sincronizza sull’evento Depositato e rileva l’evento Prelevato ----------------------------------------------------------------- */ Prelevato.Count = 1: Via libera al produttore Depositato.Count = 0: Blocca il consumatore
void Produttore() begin string Dato; do
void Consumatore() begin string Dato; do
Produce(Dato); Wait( Prelevato ); Buffer = Dato; Signal( Depositato );
Wait( Depositato ); Dato = Buffer; Signal( Prelevato ); Consuma(Dato);
while (CiSonoDati); end;
while (CiSonoDati); end;
35
Reti e problematiche di rete - Enrico Cavalli - Università di Bergamo
Produttore consumatore con un buffer circolare string Buffer[10]; 0 1 2
xxxxxx xxxxxx 5 xxxxxx 6 xxxxxx 3 4
Produttore
NextIn=(NextIn+1)% N; NextIn
NextOut
NextOut=(NextOut+1)% N; Consumatore
7 8 9
Buffer
/* ----------------------------------------------------------------- * * Programma BufferCircolare * * ----------------------------------------------------------------- */ void main() begin const N = ...; Pieno permette di inserire N dati nel buffer prima di fermare il produttore string Buffer[N]; int NextIn(=0), NextOut(=0); Vuoto blocca subito il consumatore semaforo Pieno(Count=N) semaforo Vuoto(Count=0); void Produttore(); void Consumatore(); parbegin Produttore(); Consumatore(); parend; end; Reti e problematiche di rete - Enrico Cavalli - Università di Bergamo
36
18
Produttore consumatore con un buffer circolare Pieno.Count = N
via libera N volte al produttore
Vuoto.Count = 0
blocca subito il consumatore
NextIn = NextOut = 0
/*----------------------------------------------------------------------------*/ void Produttore() begin string Dato; do Produce(Dato); Wait(Pieno); Buffer[NextIn] = Dato; NextIn = (NextIn+1) % N; Signal(Vuoto);
void Consumatore() begin string Dato; do Wait(Vuoto); Dato = Buffer[NextOut]; NextOut = (NextOut+1) % N; Signal(Pieno); Consuma(Dato);
while (CiSonoDati); end;
while (CiSonoDati); end;
Le corse critiche si possono avere solo se NextIn = NextOut cioè in caso di buffer vuoto oppure di buffer pieno. Supponiamo che il buffer sia pieno, per esempio perché il consumatore è fermo: allora Pieno.Count = 0 e il produttore non può accedere alla sezione critica. Se invece il buffer è vuoto si ha: Vuoto.Count = 0 e il consumatore si ferma prima di accedere alla sezione critica per effetto di Wait(Vuoto).
Reti e problematiche di rete - Enrico Cavalli - Università di Bergamo
37
Produttore consumatore con un buffer circolare /* -----------------------------------------------------------------------Attenzione: se ci sono più consumatori e/o più produttori* * Programma ProduttoriConsumatori * l’accesso al buffer e l’aggiornamento dei puntatori devono * -----------------------------------------------------------------------*/ avvenire in mutua esclusione. Si introduce il semafori Mutex void main() inizializzato con Mutex.Count = 1 begin const N = ...; string Buffer[N]; int NextIn, NextOut; semaforo Pieno(Count=N), Vuoto(Count=0), Mutex(Count=1); void Produttore(1); void Produttore(2); ... void Consumatore(1); ... parbegin Produttore(1); Produttore(2); .. ; Consumatore(1); Consumatore(2); .. ; parend; end; /* ------------------------------------------------------------------------- */ void Produttore(i) void Consumatore(i) begin begin string Dato; string Dato; do do Produce(Dato); Wait(Vuoto); Wait(Pieno); Wait(Mutex); Wait(Mutex); Dato = Buffer[NextOut]; Buffer[NextIn] = Dato; NextOut = (NextOut+1)%N; NextIn = (NextIn+1)%N; Signal(Mutex); Signal(Mutex); Signal(Pieno); Signal(Vuoto); Consuma(Dato); while (CiSonoDati); while (CiSonoDati); end; end;
Reti e problematiche di rete - Enrico Cavalli - Università di Bergamo
38
19
Il problema dei lettori e degli scrittori Scrittore Lettore Scrittore
Area Dati Lettore
Scrittore
Un insieme di processi accedono a dati condivisi, un file o il record di un file, per esempio. I processi scrittori, inseriscono nuovi dati e/o modificano quelli esistenti, mentre i processi lettori si limitano a leggere i dati, senza modificarli. - La scrittura dei dati può essere fatta da un solo processo per volta - La lettura dei dati può essere effettuata da più lettori a condizione che non ci siano processi scrittori che stanno modificando i dati - Quando uno scrittore accede ai dati nessun lettore o scrittore può accedervi Sono regole abbastanza naturali e sono lo standard nell’accesso ai dati di un database
Reti e problematiche di rete - Enrico Cavalli - Università di Bergamo
39
Il problema dei lettori e degli scrittori /* ----------------------------------------------------------------- * * Programma LettoriScrittori * * ----------------------------------------------------------------- */ void main() Lettori: contatore dei lettori begin Scrivi: le operazioni di scrittura devono int Lettori; avvenire in mutua esclusione semaforo Scrivi(Count=1); Mutex: mutua esclusione nei conteggi semaforo Mutex(Count=1); Lettori = 0; parbegin Lettore(); Lettore(); Scrittore(); parend; end; /* ---------------------------------------------------------------- */ void Lettore() begin do void Scrittore() Wait(Mutex); begin Lettori==1: il primo lettore Lettori++; do if(Lettori==1)Wait(Scrivi);endif; Wait(Scrivi); Signal(Mutex); ScriveDati; LeggeDati; Signal(Scrivi); Wait(Mutex); forever; Lettori==0: non ci sono più lettori Lettori--; end; if(Lettori==0)Signal(Scrivi);endif; Signal(Mutex); Priorità ai lettori forever; end; Reti e problematiche di rete - Enrico Cavalli - Università di Bergamo
40
20
Il problema del barbiere che dorme Il negozio, gestito da un solo barbiere, è dotato di una poltrona da barbiere, ma ci sono un certo numero di sedie per i clienti in attesa. In assenza di clienti il barbiere si accomoda sulla sedia di lavoro e dorme, diversamente il barbiere li serve in ordine di arrivo. I clienti entrano nel negozio e, se ci sono posti a sedere liberi si accomodano, diversamente lasciano immediatamente il negozio
/* ---------------------------------------------------------------- * * Programma NegozioDelBarbiere * * ---------------------------------------------------------------- */ void main() begin const MaxClienti = ...; NumClienti: conta i clienti in attesa int NumClienti; Cliente: riconosce la presenza di clienti semaforo Mutex(Count=1); semaforo Barbiere(Count=1); Barbiere: rileva la disponibilità del barbiere semaforo Cliente(Count=0); Mutex: mutua esclusione conteggio clienti NumClienti = 0; parbegin Barbiere(); Cliente(); Cliente(); ... Cliente(); parend; end;
Reti e problematiche di rete - Enrico Cavalli - Università di Bergamo
41
Il problema del barbiere che dorme void Cliente() void Barbiere() begin begin EntraNelNegozio; AperturaNegozio; Wait(Mutex); do if (NumClienti < MaxClienti) Wait(Cliente); NumClienti++; Wait(Mutex); Se Signal(Mutex); NumClienti--; Signal(Cliente); NumClienti ≥ MaxClienti Signal(Mutex); il cliente lascia il negozio Wait(Barbiere); TaglioCapelli; AccedeAlTaglio; Signal(Barbiere); else forever; Signal(Mutex); end; endif; EsceDalNegozio; end; Anche in questo problema c’è una doppia sincronizzazione tra cliente e barbiere. Il cliente deve segnalare il proprio arrivo e attendere che il barbiere sia libero. Il barbiere deve segnalare la propria disponibilità e attendere che ci sia un cliente
Reti e problematiche di rete - Enrico Cavalli - Università di Bergamo
42
21
Costrutti linguistici per la concorrenza
Esigenza di costrutti del linguaggio semaforo Mutex(Count=1); int Posti(=0); /* -------------------------------------------------------------- */ void P(int J) begin Mutua Esclusione: Semaforo inizializzato a 1 . . . Wait: in ingresso alla sezione critica Wait(Mutex); Signal: in uscita dalla sezione critica Posti = Posti + 1; Signal(Mutex); . . . end; Cosa succede se erroneamente: 1. Si inizializza Mutex a 0 2. Si omette Signal in uscita dalla sezione critica 3. Si omette Wait in ingresso alla sezione critica 4. Si scambiano Wait con Signal
Reti e problematiche di rete - Enrico Cavalli - Università di Bergamo
44
22
I monitor Il monitor è un costrutto del linguaggio di programmazione con il quale si definisce un modulo software che contiene al proprio interno dati e procedure per manipolarli e codice per inizializzarli, oltre a speciali variabili dette variabili condizione.
monitor NomeMonitor begin Variabili e Variabili VariabiliDelMonitor; Condizione del monitor condition VariabiliCondizione; /* ---------------------------------------------------------------------- */ void ProceduraMonitor1( .. ) begin . . . end; void ProceduraMonitor2( .. ) begin . . . end; Procedure di monitor . . . void ProceduraMonitorN( .. ) begin . . . end; /* ---------------------------------------------------------------------- */ begin Inizializzazioni InizializzazioneVariabiliModulo; end; end;
45
Reti e problematiche di rete - Enrico Cavalli - Università di Bergamo
I monitor Variabili e procedure di monitor sono caratterizzate da: - Le variabili del monitor sono accessibili solo attraverso le procedure di monitor - Le procedure di monitor sono eseguite in mutua esclusione - Una sola procedura può essere attiva in un dato momento - Un processo P è entrato nel monitor quando riesce a eseguire una procedura di monitor. - Se più processi cercano di entrare contemporaneamente nel monitor uno solo riesce ad accedere al monitor, l’altro è inserito in una coda di attesa abbinata al monitor - Le variabili di condizione sono manipolabili solo con Wait(Variabile) per sospendere i processi su code abbinate alle variabili e Signal(Variabile) per riattivare i processi in attesa nella coda abbinata alla variabile - Per richiamare una procedura di monitor si usa la notazione: NomeMonitor.NomeProcedura(Parametri); I monitor sono stati implementati in alcuni linguaggi tra cui: Java, Concurrent C, Concorrent Pascal e Modula 2. Reti e problematiche di rete - Enrico Cavalli - Università di Bergamo
46
23
I monitor A, B M
X, Y, Z sono in attesa su C2
Dati del monitor
X
sono in attesa su C1
A
B
C1
Q, R, S sono in attesa di entrare nel monitor
Y
Z
C2
P
è nel monitor
Procedura1 Q
R
S
Procedura2 P
Procedura3
Inizializzazioni
Il monitor è un tipo di dato astratto con due particolari caratteristiche: la presenza delle variabili condizione e il fatto che le procedure di monitor sono eseguite in mutua esclusione.
Reti e problematiche di rete - Enrico Cavalli - Università di Bergamo
47
Mutua esclusione con i monitor /* ----------------------------------* * definizione del monitor Posti * * ----------------------------------*/ monitor Posti PostiLiberi: begin int PostiLiberi; variabile privata di Posti /*-----------------------------*/ void AssegnaPosto(boolean &Ok) begin if (PostiLiberi > 0) PostiLiberi--; Ok = true; else Ok = false; AssegnaPosto: procedura del monitor Posti endif; end; /*-----------------------------*/ begin PostiLiberi = 350; end; end; Inizializzazioni del monitor
/* -------------------------------- * * Il programma Prenotazioni * * -------------------------------- */ void main() begin monitor Posti; void Cassa(); parbegin Cassa(); ... ; Cassa(); parend; La mutua esclusione si ottiene end; trasformando tutte le sezioni /* -------------------------------*/ void Cassa() critiche in procedure di monitor begin boolean Eseguito; do ArrivaSpettatore; Posti.AssegnaPosto(Eseguito); if (Eseguito) ... else ... endif; forever; end;
Reti e problematiche di rete - Enrico Cavalli - Università di Bergamo
48
24
Sincronizzazione con i monitor Per risolvere problemi di sincronizzazione si usano le variabili condizione e le speciali operazioni Wait e Signal nel seguente modo:
• si introduce E, variabile condizione abbinata all’evento • la procedura che rileva l’evento lo segnala con l’operazione Signal(E) • la procedura che si deve sincronizzare sull’evento usa Wait(E) per sospendersi in attesa dell’evento. Le operazioni Wait e Signal dei monitor differiscono dalle omonime operazioni sui semafori per l’assenza del meccanismo di conteggio dei segnali inviati con Signal:
• Wait ferma sempre il processo che la esegue • Signal(E) cerca sempre di riattivare un processo nella coda associata alla variabile condizione E. Se la coda associata a E è vuota, il segnale viene perso.
49
Reti e problematiche di rete - Enrico Cavalli - Università di Bergamo
Sincronizzazione con i monitor Produttore Dato Produttore
Consumatore
Buffer
/* ----------------------------------------------------------------------- * * Il programma ProduttoreConsumatore * * ----------------------------------------------------------------------- */ void main() begin monitor Buffer; void Produttore(); void Consumatore(); parbegin Produttore(); Produttore(); Consumatore(); parend; end; /* ------------------------------------------------------------------- */ void Produttore() void Consumatore() begin begin string Dato; string Dato; do do Produce(Dato); Buffer.Preleva(Dato); Buffer.Inserisci(Dato); Usa(Dato); forever; forever; end; end; Reti e problematiche di rete - Enrico Cavalli - Università di Bergamo
50
25
Sincronizzazione con i monitor monitor Buffer begin string Serbatoio; boolean Pieno; condition Inserimenti, Prelievi; Inserisci: /* -------------------------------------------------------------- */ Se il buffer è vuoto deposita il dato void Inserisci(string Dato) diversamente fermati nella coda della begin condizione Inserimenti if (NOT Pieno) Serbatoio = Dato; Pieno = true; Signal(Prelievi); else Wait(Inserimenti); endif; end; /* -------------------------------------------------------------- */ void Preleva(String &Dato) begin if (Pieno) Dato = Serbatoio; Pieno = false; Signal(Inserimenti); else Wait(Prelievi); Preleva: endif; Se il buffer è pieno preleva un dato, end; diversamente fermati nella coda della /* -------------------------------------------------------------*/ condizione Prelievi begin Pieno = false; end; end; // monitor Buffer
Reti e problematiche di rete - Enrico Cavalli - Università di Bergamo
51
Costrutti linguistici per sistemi distribuiti
26
Scambio di messaggi I problemi di concorrenza in ambienti distribuiti si possono risolvere mediante lo scambio di messaggi. Le primitive tipiche per realizzare lo scambio di messaggi sono Send e Receive: Send(Destinazione, Messaggio) Receive(Sorgente, Messaggio) Send invia un messaggio a un destinatario, mentre Receive serve a ricevere un messaggio da una determinata sorgente: P0 invia un messaggio a un processo P1, mentre P1 riceve un messaggio da P0.
P0
P1
Send(P1,msg)
Receive(P0,msg)
Lo scambio di messaggi si può usare nei sistemi distribuiti e negli ambienti mono e multi processore a memoria condivisa. Il canale di comunicazione è una connessione fisica per i sistemi distribuiti, mentre è un’area di memoria gestita dal sistema operativo per i sistemi a memoria condivisa.
Reti e problematiche di rete - Enrico Cavalli - Università di Bergamo
53
Messaggi e Mailbox
Destinatario Mittente Tipo Messaggio Priorità Lunghezza Messaggio Dati di Controllo
Messaggio
Si è fatto riferimento all’indirizzamento diretto nel quale mittente e destinatario sono specificati esplicitamente nelle primitive di comunicazione. Un approccio alternativo è quello dell’indirizzamento indiretto secondo il quale i messaggi sono inviati a una struttura dati condivisa da mittente e destinatario detta mailbox (casella postale) o porta. Questo approccio è particolarmente utile nelle situazioni dove il ricevente deve accettare tutti i messaggi che gli sono stati inviati anche se non conosce i processi mittenti. Questo avviene, per esempio, nel caso di un database server che deve rispondere alle richieste di tutti i processi client.
Reti e problematiche di rete - Enrico Cavalli - Università di Bergamo
54
27
Messaggi e Mailbox C1
Send( Mailbox, Msg )
C2 Receive( Mailbox, Msg )
S C3 C4
Mailbox
I processi spediscono messaggi alle mailbox e li ricevono prelevandoli dalla mailbox. La mailbox memorizza i messaggi in apposite code che sono gestite con diverse modalità, tenendo conto, per esempio, della priorità dei messaggi inviati alla mailbox. Per esempio: quattro processi client richiedono i servizi a un processo server S inviando messaggi a una mailbox. Il processo server riceve i messaggi dalla mailbox, esamina le richieste e risponde in modo opportuno. Una mailbox contiene una quantità finita di messaggi: conveniamo che se un processo invia un messaggio a una mailbox piena si ferma in attesa che un processo rimuova un messaggio dalla mailbox. Reti e problematiche di rete - Enrico Cavalli - Università di Bergamo
55
Send e Receive Deve essere prevista una qualche forma di sincronizzazione nella spedizione e ricezione dei messaggi. Per farlo bisogna definire il comportamento di Send e Receive al momento dell’esecuzione.
•
•
• •
La Send è bloccante se il processo che la esegue si blocca fino a quando il messaggio è stato ricevuto (e ne ha avuto conferma tramite una sorta di “ricevuta di ritorno”). La primitiva Send è invece non bloccante se il processo, dopo avere spedito un messaggio, prosegue nella normale esecuzione. Anche la Receive può essere bloccante oppure non bloccante. Si pensi a un processo P che esegue Receive(Q, msg). Nel caso di Receive bloccante, il processo P si ferma in attesa di ricevere il messaggio inviato da Q. Nel caso di Receive non bloccante, si hanno due possibilità: se il messaggio è già stato inviato P lo riceve e prosegue mentre, se Q non ha inviato il messaggio, P prosegue senza ricevere il messaggio. In questo caso, in genere, P emette un messaggio di errore. Il caso Send bloccante con Receive bloccante stabilisce una forma di sincronizzazione stretta tra mittente e ricevente e si parla di rendez-vous (incontro in francese) tra i due processi che rimangono bloccati fino a trasmissione del messaggio avvenuta. Il caso Send non bloccante con Receive bloccante è molto diffuso, per esempio, nei processi server dove bisogna rispondere rapidamente alle richieste dei processi client.
Reti e problematiche di rete - Enrico Cavalli - Università di Bergamo
56
28
Sincronizzazione con rendez-vous Nel caso di Send bloccante con Receive bloccante (rendez-vous) si stabilisce una forma di sincronizzazione stretta tra mittente e ricevente. In caso di rendez-vous due processi, per esempio un processo produttore e un processo consumatore, si possono sincronizzare nel seguente modo:
/* ------------------------------------------------------------* Sincronizzazione con Rendez-vous * ------------------------------------------------------------- */ void Produttore() void Consumatore() begin begin string Dato; string Dato; do do Produce(Dato); Receive(Produttore,Dato); Send(Consumatore,Dato); Consuma(Dato); forever; forever; end; end;
Reti e problematiche di rete - Enrico Cavalli - Università di Bergamo
57
Mutua Esclusione con lo scambio di messaggi Mostriamo come risolvere i problemi base della programmazione concorrente nel caso di Send non bloccante, Receive bloccante e uso di mailbox. Mutua Esclusione con lo scambio di messaggi* /* ---------------------------------------------------------------------* MutuaEsclusione con lo scambio di messaggi * * ---------------------------------------------------------------------- */ void main() begin mailbox Casella; Mutua Esclusione: Invio di void P0(); void P1(); un messaggio nella mailbox CreazioneMailbox(Casella); per iniziare Send(Casella, ‘’); parbegin P0(); P1(); parend; end; /* ------------------------------------------------------------------ */ void P0() void P1() begin begin string Msg; string Msg; do do Receive(Casella,Msg); Receive(Casella,Msg); // SezioneCritica; // SezioneCritica; EntraCritica Receive Send(Casella,Msg); Send(Casella,Msg); EsciCritica Send AltroCodice; AltroCodice; forever; forever; end; end; Reti e problematiche di rete - Enrico Cavalli - Università di Bergamo
58
29
Programmazione concorrente con lo scambio di messaggi Soluzione dei problemi base della programmazione concorrente con lo scambio di messaggi: Send non bloccante, Receive bloccante e uso di mailbox.
Mutua Esclusione con lo scambio di messaggi Per implementare la mutua esclusione con i messaggi si usa una mailbox che contiene un solo messaggio. La porzione di codice che deve essere eseguita in mutua esclusione deve essere delimitata in entrata con una Receive bloccante e in uscita da una Send non bloccante inviate a quella casella.
Sincronizzazione con lo scambio di messaggi Per sincronizzare processi con lo scambio di messaggi si procede nel seguente modo: il processo che rivela l’evento invia un messaggio con Send non bloccante a una mailbox vuota, mentre il processo che si deve sincronizzare sull’evento esegue una Receive bloccante su quella mailbox 59
Reti e problematiche di rete - Enrico Cavalli - Università di Bergamo
Sincronizzazione con lo scambio di messaggi Sincronizzazione con lo scambio di messaggi /* ---------------------------------------------------------------------- * * Sincronizzazione con lo scambio di messaggi * * ---------------------------------------------------------------------- */ void main() begin mailbox Casella; void P0(); void P1(); CreazioneMailbox(Casella); parbegin P0(); P1(); parend; end; /* -----------------------------------------------------------------*/ P0 rimane bloccato da Receive sino a quando P1 invia un messaggio void P0() begin string Msg(=‘ ‘); AzioniPreEvento; Receive(Casella,Msg); AzioniDopoEvento; end;
Reti e problematiche di rete - Enrico Cavalli - Università di Bergamo
void P1() begin string Msg(=‘ ’); RilevaEvento; Send(Casella,Msg); AltreOperazioni; end;
60
30
Esempio: produttore - consumatore Doppia sincronizzazione /* ---------------------------------------------------------------------- * * Produttore-Consumatore con lo scambio di messaggi * * ---------------------------------------------------------------------- */ void main() begin mailbox Consumato, Prodotto; string Msg; void Produttore(); void Consumatore(); CreaMailbox(Prodotto); CreaMailbox(Usato); Via libera al produttore Send(Usato,‘’); Bloccato il consumatore parbegin Produttore(); Consumatore(); parend; end; /* ------------------------------------------------------------------- */ void Produttore() begin string Dato; do Produce(Dato); Receive(Usato,Msg); Msg = Dato; Send(Prodotto,Msg); forever; end;
void Consumatore() begin string Dato; do Receive(Prodotto, Msg); Send: Rileva evento Dato = Msg; Receive: Si sincronizza Send(Usato,Msg); sull’evento Consuma(Dato); forever; end;
Reti e problematiche di rete - Enrico Cavalli - Università di Bergamo
61
Domande(1) 1.
I processi P0 e P1 sono eseguiti in parallelo e cooperano come indicato di seguito: /*-------------------------------------------------------* P0 esegue P1 esegue *-------------------------------------------------------- */ do do ProduceDati; EntraCritica; EntraCritica; LeggeBuffer; ScriveBuffer; EsceCritica; EsceCritica; StampaDati; forever; forever; L’accesso in mutua esclusione al buffer è garantito? La sincronizzazione è garantita? Cosa può accadere se il processo P0 è più veloce del processo P1? Cosa può accadere se il processo P0 è più lento del processo P1?
2.
Quali delle seguenti affermazioni sono vere (V) e quali sono false (F) - I semafori sono speciali variabili pensate per implementare il meccanismo attesa risveglio fra processi concorrenti - I semafori sono rappresentabili come un intero e una coda di processi - I semafori sono una struttura normalmente programmabile - La primitiva Wait decrementa la variabile intera del semaforo - Se il contatore di un semaforo vale -5 allora ci sono 4 processi in coda sul semaforo - Se il contatore di un semaforo è >= 0 non ci sono processi in coda su quel semaforo
Reti e problematiche di rete - Enrico Cavalli - Università di Bergamo
62
31
Domande(2) 3.
Il valore di Count del semaforo S è 4. Un processo P esegue per tre volte Wait(S) e subito dopo il processo Q esegue due Wait(S). Quali sono lo stato di P e di Q?
4.
Il valore di Count del semaforo S è 0. Un processo P esegue Wait(S) e poco dopo il processo Q esegue Signal(S). Quali sono lo stato di P e di Q?
5.
Il valore di Count del semaforo S è -2 e il processo P è in attesa nella coda associata ad S. Un processo Q esegue Signal(S). Quali sono lo stato di P e di Q?
6.
I processi P e Q competono per accedere a una sezione critica secondo il seguente codice che usa il semaforo Mutex inizializzato a 1. /* ------------------------------------------------------------------------* P Q * ------------------------------------------------------------------------- */ . . . . . . Wait(Mutex); Wait(Mutex); //SezioneCritica; //SezioneCritica; Signal(Mutex); Wait(Mutex); . . . . . .
Commentate la soluzione. Commentate la soluzione nel caso di Mutex inizializzato a 0 ed a 2.
63
Reti e problematiche di rete - Enrico Cavalli - Università di Bergamo
Domande(3) 7.
Il processo P si deve sincronizzare con Q attraverso un evento rilevato da Q. Per farlo si usa un semaforo S e il seguente codice: /* --------------------------------------------------------------- * * P Q * * --------------------------------------------------------------- */ . . . . . . AzioniPreEvento; RilevaEvento; Wait(S); Signal(S); AzioniDopoEvento; AltreOperazioni; . . . . . . Commentate la soluzione nel caso si S inizializzato a 0 ed a 1.
8.
Nel programma ProduttoreConsumatore si usano: Depositato(Count=0) e Prelevato(Count=1): void Produttore() begin string Dato; do Produce(Dato); Wait( Prelevato ); Buffer = Dato; Signal( Depositato ); while (CiSonoDati); end;
void Consumatore() begin string Dato; do Wait( Depositato ); Dato = Buffer; Signal( Prelevato ); Consuma(Dato); while (CiSonoDati); end;
Commentate la soluzione. Commentate la soluzione nel caso che i due semafori siano inizializzati come Depositato(Count=1) e Prelevato(Count=0). Nel programma ProduttoreConsumatore a cosa serve Signal(Prelevato) ?
Reti e problematiche di rete - Enrico Cavalli - Università di Bergamo
64
32
Domande(4) 9.
Nel monitor M ci sono due procedure P e Q. Due processi eseguono contemporaneamente M.P e M.Q. Cosa succede?
10.
Due processi P e Q si vogliono sincronizzare su un evento E rilevato da P usando la primitiva Send non bloccante, Receive bloccante e una mailbox di nome Casella. Per farlo: a. - P esegue Send(Casella , msg) Q esegue Send(Casella, msg) b. - P esegue Send(Casella, msg) Q esegue Receive(Casella, msg) c. - P esegue Receive(Casella, msg) Q esegue Receive(Casella, msg) d. - P esegue Receive(Casella, msg) Q esegue Send(Casella, msg)
11.
Due processi P e Q devono accedere in mutua esclusione a una regione critica usando la primitiva Send non bloccante e Receive bloccante e una mailbox di nome Casella che contiene un solo messaggio. Per farlo entrambi i processi circondano la sezione critica: a. b. c. d.
-
Con Receive(Casella, msg) prima della regione critica Con Receive(Casella, msg) prima della regione critica Con Send(Casella, msg) prima della regione critica Con Send(Casella, msg) prima della regione critica
Send(Casella, msg) dopo Receive(Casella, msg) dopo Receive(Casella, msg) dopo Send(Casella, msg) dopo
Reti e problematiche di rete - Enrico Cavalli - Università di Bergamo
65
Esercizi(1) 1.
I processi P0 e P1, eseguiti in parallelo, sono composti da istruzioni che consideriamo indivisibili. /*-------------------------------------------------------* P0 esegue P1 esegue *-------------------------------------------------------- */ begin begin A = A + 1; A = A * 3; end; A = A - 1; end;
Se il valore iniziale di A è 3 quali sono i possibili valori di A dopo l’esecuzione concorrente dei due processi P0 e P1
2.
Modificare il programma ContaPostiLiberi (diapositiva 12) in modo che possa funzionare con più casse. Naturalmente la soluzione sarà soggetta alle medesime limitazioni del programma originale.
3.
Nel programma ContaPostiLiberi si vogliono contare il numero di posti ancora liberi e quello dei posti occupati. Per farlo si usano due variabili Liberi e Occupati che devono essere aggiornate dai processi P0 e P1. Si progettino due possibili soluzioni al problema usando le speciali istruzioni EntraCritica ed EsciCritica. Una delle due soluzioni deve garantire che in ogni momento sia rispettato il vincolo di integrità: Liberi+Occupati = 500 e l’altra che non garantisca questa condizione.
4.
Mostrate come sincronizzare due processi con l’istruzione TSL
Reti e problematiche di rete - Enrico Cavalli - Università di Bergamo
66
33
Esercizi(2) 5.
Si mostri come risolvere, con i semafori, il problema dell’accesso a due regioni critiche RCX ed RCY da parte dei due processi concorrenti P e Q che interagiscono nel seguente modo: /*----------------------------------------------------------------* P Q *----------------------------------------------------------------- */ do do Azioni1_P; Azioni1_Q; RCX_P; RCY_Q; Azioni2_P; Azioni2_Q; RCY_P; RCX_Q; Azioni3_P; Azioni3_Q; forever; forever;
La regione RCX indica una sezione critica nella quale si accede alla variabile condivisa X mentre RCY indica una sezione critica nella quale si accede alla variabile condivisa Y
6.
In alcuni processori è disponibile un’istruzione macchina che scambia il valore di un dato in memoria con quello di un registro. L’istruzione di scambio può essere definita formalmente come: void Scambia(boolean &X, boolean &Y) begin boolean Temp; Temp = X; X = Y; Y = Temp; end; Mostrate come risolvere il problema della mutua esclusione con l’istruzione Scambia
Reti e problematiche di rete - Enrico Cavalli - Università di Bergamo
67
Esercizi(3) 7.
I semafori binari sono semafori nei quali la variabile Count può assumere i soli valori 0 ed 1 e le operazioni Wait e Signal per i semafori binari sono definite da: /* ------------------------------------------------------------------------- * * Wait per i semafori binari * * ------------------------------------------------------------------------- */ void Wait(semaforoBinario &S) begin if ( S.Count == 1 ) S.Count = 0; else InserisciProcesso(S.Coda); BloccaProcesso; endif; end; /* ------------------------------------------------------------------------- * * Signal per i semafori binari * * ------------------------------------------------------------------------- */ void Signal(semaforoBinario &S) begin processo P; if ( S.Coda è vuota ) S.Count = 1; else P = EstraiProcesso(S.Coda); AttivaProcesso(P); endif; end;
Descrivete con le vostre parole il comportamento di un semaforo binario e mostrate come implementarlo usando i semafori come sono stati definiti nel corso.
Reti e problematiche di rete - Enrico Cavalli - Università di Bergamo
68
34
Esercizi(4) 8.
Dopo aver controllato come implementare le procedure Wait e Signal con con TestSet mostrate come realizzarle con Scambia
9. 10. 11. 12.
Risolvete l problema presentato nell’esercizio 3 con i semafori Risolvere il problema trattato in EstrattoConto (diapositive 8 e 9) con i semafori Risolvere il problema trattato in ContaPostiLiberi (diapositiva 12) con i semafori Dopo aver controllato come implementare le procedure Wait e Signal con con TestSet mostrate come realizzarle sfruttando le interruzioni
13.
Dite per quale ragione è ragionevole implementare i semafori con le interruzioni e in quale situazione è invece impossibile usare le interruzioni per implementare i semafori.
14.
Dite se è possibile realizzare Wait e Signal in software e nel caso riteniate sia possibile mostrare come farlo.
15.
Un ponte sul quale si può viaggiare in un solo senso per volta, attraversa un fiume in direzione nordsud. Implementare con i semafori la gestione dell’accesso al ponte secondo le seguenti regole: il passaggio da nord a sud è possibile se sul ponte non ci sono veicoli che viaggiano in senso opposto; il passaggio da sud a nord è possibile solo se il ponte è libero e non ci sono veicoli in attesa nell’altro senso. (Suggerimento: controllate accuratamente il testo e confrontatelo con quello del problema LettoriScrittori)
Reti e problematiche di rete - Enrico Cavalli - Università di Bergamo
69
Esercizi(5) 16.
Un ponte molto breve, sul quale passa una sola automobile per volta, attraversa un fiume in direzione nord-sud. Implementare con i semafori la gestione dell’accesso al ponte secondo le seguenti regole: il passaggio da nord a sud è possibile se il ponte è libero; il passaggio da sud a nord è possibile solo se il ponte è libero e non ci sono veicoli in attesa nell’altro senso. In poche parole: senso unico alternato con precedenza ai veicoli da nord a sud.
17.
Si consideri un ascensore che trasporta i visitatori a una torre. I passeggeri accedono all’ascensore, in grado di trasportare 50 passeggeri per volta, passando attraverso due tornelli. Raggiunto il numero massimo di visitatori trasportabili i tornelli si bloccano e l’ascensore parte. Risolvete il problema di controllare i tornelli e l’ascensore supponendo un flusso continuo di visitatori
18.
Risolvete con i semafori il problema di un processo produttore e un processo consumatore che interagiscono, usando due buffer, secondo lo schema: /*----------------------------------------------------------------* P0 esegue P1 esegue *----------------------------------------------------------------- */ do do ProduceDati1; LeggeBuffer1; ScriveBuffer1; UsaDati1; ProduceDati2; LeggeBuffer2; ScriveBuffer1; UsaDati1; forever; forever;
Reti e problematiche di rete - Enrico Cavalli - Università di Bergamo
70
35
Esercizi(6) 19.
Il problema dei filosofi a tavola ha la seguente formulazione: Ci sono 5 filosofi che vivono assieme e passano il loro tempo pensando o mangiando. Per cibarsi si accomodano a un tavolo circolare apparecchiato con cinque piatti ripieni di spaghetti. Di fianco a ogni piatto c’è una forchetta per magiare la pasta. Quando un filosofo vuole mangiare prende la forchetta alla destra e quella alla sinistra del piatto e mangia. Gestire la situazione con i semafori. Ci sono problemi di mutua esclusione perché la forchetta alla sinistra di un piatto è anche la forchetta alla destra di un altro e di conseguenza due filosofi che si siedono uno di fianco all’altro non possono mangiare contemporaneamente. semaforo Forchetta[5](Count=1); void Filosofo(int J) begin do Pensa; Wait(Forchetta[J]); Wait(Forchetta([(J+1)%5]); PrendePosate; Mangia; DepositaPosate; Signal(Forchetta([(J+1)%5]); Signal(Forchetta[J]); forever; end; La procedura in figura mostra il comportamento di uno dei filosofi in una possibile soluzione che usa un array di semafori inzializzati a 1. Studiate la soluzione è spiegate che non è accettabile perché può portare allo stallo.
Reti e problematiche di rete - Enrico Cavalli - Università di Bergamo
71
Esercizi(7) 20.
C’è una soluzione al problema dei filosofi a tavola che evita lo stallo: un filosofo prima di impugnare le forchette controlla che siano entrambe disponibili e poi le prende e mangia. Lo schema di soluzione è il seguente: semaforo Mutex(Count = 1); void Filosofo(int J) begin do Pensa; Wait(Mutex); if (ci sono le posate) PrendePosate; Mangia; DepositaPosate; endif Signal(Mutex); forever; end;
Scrivete il codice completo della soluzione. Per controllare l’esistenza delle posate servirà un array di flag per sapere se una posata è libera o in uso. Spiegare perché con la soluzione proposta c’è un solo filosofo alla volta che mangia e c’è il pericolo di ritardo indefinito.
21.
Affinate la soluzione proposta nel precedente esercizio in modo che ci sia più di un filosofo che sta mangiando. Per farlo osservate che la mutua esclusione deve riguardare solo il momento nel quale si decide di mangiare e si impugnano le posate e non quando si mangia. Anche in questa soluzione c’è il rischio di ritardo indefinito.
22.
Risolvere il problema discusso in EstrattoConto (diapositive 8-9) con i monitor.
Reti e problematiche di rete - Enrico Cavalli - Università di Bergamo
72
36
Esercizi(8) 23. 24. 25. 26. 27.
Risolvere il problema dei Lettori/Scrittori (diapositiva 40) con i monitor.
28.
Risolvere, mediante lo scambio di messaggi, il problema di un processo produttore e un processo consumatore che si scambiano dati per mezzo di un buffer in grado di memorizzare sino a 20 oggetti prodotti.
Risolvere il problema della salita in ascensore dell’esercizio 17 con i monitor. Risolvere il problema del produttore/consumatore con un buffer circolare usando i monitor. Risolvere il problema discusso in EstrattoConto (diapositive 8-9) con lo scambio di messaggi Risolvere il problema di un processo produttore e un processo consumatore che interagiscono, usando due buffer, secondo lo schema dell’esercizio 18, con lo scambio di messaggi.
Reti e problematiche di rete - Enrico Cavalli - Università di Bergamo
73
37