Concetto di processo



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

GESTIONE DEI PROCESSI

Università degli Studi della Calabria Corso di Laurea in Ingegneria Informatica A.A. 2001/2002. Sistemi Operativi Corsi A e B. Esercitazioni 7 e 8

Università degli Studi della Calabria Corso di Laurea in Ingegneria Informatica A.A. 2001/2002. Sistemi Operativi Corsi A e B. Esercitazioni 3 e 4

Esercitazioni 3 e 4. Sincronizzazione dei Processi (2 a parte)

Java Virtual Machine

Java threads (2) Programmazione Concorrente

Corso di Laurea in Ingegneria Informatica. Sistemi Operativi (Corsi A e B)

Corso di Laurea in Ingegneria Informatica. Corso di Reti di Calcolatori A.A. 2009/2010

Monitor. Introduzione. Struttura di un TDA Monitor

Il costrutto monitor [Hoare 74]

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

Multithreading in Java. Fondamenti di Sistemi Informativi

Realizzazione di Politiche di Gestione delle Risorse: i Semafori Privati

Capitolo 7: Sincronizzazione

Pronto Esecuzione Attesa Terminazione

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:

Il costrutto monitor [Hoare 74]

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

SISTEMI OPERATIVI. Sincronizzazione dei processi. Domande di verifica. Luca Orrù Centro Multimediale Montiferru 30/05/2007

CAPITOLO 27 SCAMBIO DI MESSAGGI

Coordinazione Distribuita

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

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

19. Introduzione al multi-threading

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

Ottava Esercitazione. introduzione ai thread java mutua esclusione

Un esercizio d esame. Flavio De Paoli

Telematica II 17. Esercitazione/Laboratorio 6

Introduzione ai Metodi Formali

Sistema Operativo. Fondamenti di Informatica 1. Il Sistema Operativo

Il Sistema Operativo

Sommario. G. Piscitelli

Sommario. G. Piscitelli

Sistemi Operativi (modulo di Informatica II)

ESERCIZIO 1 (b) Dove è memorizzato il numero del primo blocco del file? Insieme agli altri attributi del file, nella cartella che contiene il file.

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

Introduzione. Coordinazione Distribuita. Ordinamento degli eventi. Realizzazione di. Mutua Esclusione Distribuita (DME)

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

Sistemi Operativi Esercizi Sincronizzazione

Java: Compilatore e Interprete

Thread: sincronizzazione Esercitazioni del 09 Ottobre 2009

FONDAMENTI di INFORMATICA L. Mezzalira

Sistemi Operativi. Lezione 7 Comunicazione tra processi

I Thread. I Thread. I due processi dovrebbero lavorare sullo stesso testo

Il Sistema Operativo. C. Marrocco. Università degli Studi di Cassino

Sistemi Operativi. Processi GESTIONE DEI PROCESSI. Concetto di Processo. Scheduling di Processi. Operazioni su Processi. Processi Cooperanti

Il problema del produttore e del consumatore. Cooperazione tra processi

SISTEMI OPERATIVI. Sincronizzazione in Java (Monitor e variabili condizione in Java)

CAPITOLO 7 - SCAMBIO DI MESSAGGI

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

Corso di Linguaggi di Programmazione

Funzioni in C. Violetta Lonati

3 - Variabili. Programmazione e analisi di dati Modulo A: Programmazione in Java. Paolo Milazzo

T 1. Per un processo con più thread di controllo, lo stato di avanzamento della computazione di ogni thread è dato da:

Introduzione alla programmazione in C

Modello a scambio di messaggi

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

Protezione. Protezione. Protezione. Obiettivi della protezione

Esercizi sugli Oggetti Monitor

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

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

Informatica: il sistema operativo

T E O R I A D I P R O G E T T A Z I O N E D E L S O F T W A R E

Lezione 8. La macchina universale

MODELLO CLIENT/SERVER. Gianluca Daino Dipartimento di Ingegneria dell Informazione Università degli Studi di Siena

10 - Programmare con gli Array

Sistemi Operativi mod. B. Sistemi Operativi mod. B A B C A B C P P P P P P < P 1, >

Programmazione concorrente in Java

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

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

Approccio stratificato

La gestione di un calcolatore. Sistemi Operativi primo modulo Introduzione. Sistema operativo (2) Sistema operativo (1)

6. Sincronizzazione dei Processi. Esempio: Produttore- Consumatore con n elementi. Esempio: Produttore- Consumatore con n elementi

Scheduling. Sistemi Operativi e Distribuiti A.A Bellettini - Maggiorini. Concetti di base

Algebra di Boole: Concetti di base. Fondamenti di Informatica - D. Talia - UNICAL 1. Fondamenti di Informatica

Esempio produttori consumatori. Primitive asincrone

Algoritmi di Ricerca. Esempi di programmi Java

Corso di Laurea in Ingegneria Gestionale Esame di Informatica - a.a luglio 2013

13 - Gestione della Memoria nella Programmazione Orientata agli Oggetti

La Gestione delle risorse Renato Agati

B+Trees. Introduzione

Soluzione dell esercizio del 2 Febbraio 2004

Meccanismi di sincronizzazione: Semafori e Monitor

Gestione della memoria centrale

Corso di Laurea in Ingegneria Gestionale Esame di Informatica a.a giugno 2013

MODELLO AD AMBIENTE GLOBALE

Gestione del processore e dei processi

Sincronizzazione tra processi

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

La selezione binaria

DTI / ISIN / Titolo principale della presentazione. La cena dei filosofi. Amos Brocco, Ricercatore, DTI / ISIN. 14 maggio 2012

CPU. Maurizio Palesi

Sistemi Operativi (modulo di Informatica II) I processi

Introduzione. Meccanismi di sincronizzazione: Semafori e Monitor. Semafori - Definizione. Semafori - Descrizione informale

Inizializzazione, Assegnamento e Distruzione di Classi

Corso di Informatica

INFORMATICA. Il Sistema Operativo. di Roberta Molinari

Esercitazione n 4. Obiettivi

Corso di Sistemi di Elaborazione delle informazioni

Transcript:

Università degli Studi della Calabria Corso di Laurea in Ingegneria Informatica Lucidi delle esercitazioni di (Corsi A e B) A.A. 2002/2003 1 Concetto di processo Nei primi sistemi di calcolo era consentita l esecuzione di un solo programma alla volta. Tale programma aveva il completo controllo del sistema e accesso a tutte le sue risorse. Gli attuali sistemi di calcolo consentono, invece, che più programmi siano caricati in memoria ed eseguiti concorrentemente. Essi sono detti sistemi time-sharing o multitasking. In tali sistemi più programmi vengono eseguiti dalla CPU, che commuta la loro esecuzione con una frequenza tale da permettere agli utenti di interagire con ciascun programma durante la sua esecuzione. L unità di lavoro dei moderni sistemi time-sharing è il processo. 2 P.Trunfio

Definizione di processo Un processo può essere definito come un programma in esecuzione. Un processo esegue le proprie istruzioni in modo sequenziale, ovvero in qualsiasi momento viene eseguita al massimo un istruzione del processo. Un programma di per sé non è un processo: un programma è un entità passiva, come il contenuto di un file memorizzato su disco, mentre un processo è una entità attiva, con un program counter che specifica l attività attuale (ovvero, quale sia la prossima l istruzione da eseguire) ed un insieme di risorse associate. Due processi possono essere associati allo stesso programma, ma sono da considerare due sequenze di esecuzione distinte. 3 P.Trunfio Processi indipendenti e cooperanti I processi in esecuzione nel sistema operativo possono essere indipendenti o cooperanti: Un processo è indipendente se non può influire su altri processi nel sistema o subirne l influsso. Un processo che non condivida dati temporanei o permanenti con altri processi è indipendente. Un processo è cooperante se influenza o può essere influenzato da altri processi in esecuzione nel sistema. Ovviamente, qualsiasi processo che condivida dati con altri processi è un processo cooperante. 4 P.Trunfio

Cooperazione tra processi Ci sono diversi motivi per fornire un ambiente che consenta la cooperazione tra processi: Condivisione di informazioni. Consente a più utenti di condividere le stesse informazioni (ad esempio un file). Accelerazione del calcolo. Si divide un problema in sottoproblemi che possono essere eseguiti in parallelo. Un accelerazione di questo tipo è ottenibile solo se il computer dispone di più elementi di elaborazione (come più CPU o canali di I/O) Modularità. Consente la realizzazione di un sistema modulare che divide le funzioni in processi distinti. 5 P.Trunfio Thread Un thread, anche detto lightweight process, è un flusso sequenziale di esecuzione di istruzioni all interno di un programma/processo. In un programma si possono far partire più thread che sono eseguiti concorrentemente. Nei computer a singola CPU la concorrenza viene simulata con una politica di scheduling che alterna l'esecuzione dei singoli thread. Tutti i thread eseguono all interno del contesto di esecuzione di un solo processo, ovvero condividono le stesse variabili del programma. Ciascun processo tradizionale, o heavyweight, ha invece il proprio contesto di esecuzione. 6 P.Trunfio

Thread in Java Tutti i programmi Java comprendono almeno un thread. Anche un programma costituito solo dal metodo main viene eseguito come un singolo thread. Inoltre, Java fornisce strumenti che consentono di creare e manipolare thread aggiuntivi nel programma Esistono due modi per implementare thread in Java: Definire una sottoclasse della classe Thread. Definire una classe che implementa l interfaccia Runnable. Questa modalità è più flessibile, in quanto consente di definire un thread che è sottoclasse di una classe diversa dalla classe Thread. 7 P.Trunfio Definire una sottoclasse della classe Thread 1. Si definisce una nuova classe che estende la classe Thread. La nuova classe deve ridefinire il metodo run() della classe Thread. 2. Si crea un istanza della sottoclasse tramite new. 3. Si chiama il metodo start() sull istanza creata. Questo determina l invocazione del metodo run() dell oggetto, e manda in esecuzione il thread associato. 8 P.Trunfio

Esempio di sottoclasse di Thread class Saluti extends Thread public Saluti(String nome) super(nome); public void run() for (int i = 0; i < 10; i++) System.out.println("Ciao da "+getname()); public class ThreadTest public static void main(string args[]) Saluti t = new Saluti("Primo Thread"); t.start(); 9 P.Trunfio Definire una classe che implementa Runnable 1. Si definisce una nuova classe che implementa l interfaccia Runnable. La nuova classe deve implementare il metodo run() dell interfaccia Runnable. 2. Si crea un istanza della classe tramite new. 3. Si crea un istanza della classe Thread, passando al suo costruttore un riferimento all istanza della nuova classe definita. 4. Si chiama il metodo start() sull istanza della classe Thread creata, determinando l invocazione del metodo run() dell oggetto Runnable associato. 10 P.Trunfio

Esempio di classe che implementa Runnable class Saluti implements Runnable private String nome; public Saluti(String nome) this.nome = nome; public void run() for (int i = 0; i < 10; i++) System.out.println("Ciao da "+nome); public class RunnableTest public static void main(string args[]) Saluti s = new Saluti("Secondo Thread"); Thread t = new Thread (s); t.start(); 11 P.Trunfio Costruttori principali: La classe Thread (1) Thread (): crea un nuovo oggetto Thread. Thread (String name): crea un nuovo oggetto Thread con nome name. Thread (Runnable dall oggetto target. target): crea un nuovo oggetto Thread a partire Thread (Runnable target, String name): crea un nuovo oggetto Thread con nome name a partire dall oggetto target. Metodi principali: String getname(): restituisce il nome di questo Thread. void join() throws InterruptedException: attende fino a quando questo Thread non termina l esecuzione del proprio metodo run. 12 P.Trunfio

La classe Thread (2) void join(long millis) throws InterruptedException: attende, per un tempo massimo di millis millisecondi, fino a quando questo Thread non termina l esecuzione del proprio metodo run. void run(): specifica le operazioni svolte dal Thread. Deve essere ridefinito dalla sottoclasse, altrimenti non effettua alcuna operazione. Se il Thread è stato costruito a partire da un oggetto Runnable, allora verrà invocato il metodo run di tale oggetto. static void sleep(long millis) throws InterruptedException: determina l interruzione dell esecuzione del Thread corrente per un tempo di millis millisecondi. void start(): fa partire l esecuzione del Thread. Viene invocato il metodo run di questo Thread. static void yield(): determina l interruzione temporanea del Thread corrente, e consente ad altri Thread di essere eseguiti. 13 P.Trunfio La classe Thread (3) static Thread currentthread(): restituisce un riferimento all oggetto Thread attualmente in esecuzione. void setpriority(int newpriority): cambia la priorità di questo Thread. int getpriority(): restituisce la priorità di questo Thread. Costanti della classe: static final int MAX_PRIORITY: la massima priorità (pari a 10) che un Thread può avere. static final int MIN_PRIORITY: la minima priorità (pari ad 1) che un Thread può avere. static final int NORM_PRIORITY: la priorità (pari a 5) che viene assegnata di default ad un Thread. 14 P.Trunfio

Ciclo di vita di un Thread yield() start() New Runnable Not Runnable Dead Il metodo run() termina New: subito dopo l istruzione new le variabili sono state allocate e inizializzate; il thread è in attesa di passare nello stato Runnable. Runnable: il thread è in esecuzione o in coda di attesa per ottenere l utilizzo della CPU. Not Runnable: il thread non può essere messo in esecuzione dallo scheduler. Entra in questo stato quando è in attesa di operazioni di I/O, oppure dopo l invocazione del metodo sleep(), o del metodo wait(), che verrà discusso in seguito. Dead: al termine dell esecuzione del suo metodo run(). 15 P.Trunfio Scheduling dei Thread La Java Virtual Machine (JVM) è in grado di eseguire una molteplicità di thread su una singola CPU: lo scheduler della JVM sceglie il thread in stato Runnable con priorità più alta; se più thread in attesa di eseguire hanno uguale priorità, la scelta dello scheduler avviene con una modalità ciclica (round-robin). Il thread messo in esecuzione dallo scheduler viene interrotto se e solo se: il metodo run termina l esecuzione; il thread esegue yield(); un thread con priorità più alta diventa Runnable; il quanto di tempo assegnato si è esaurito (solo su sistemi che supportano time-slicing). 16 P.Trunfio

class Printer private int from; private int to; public Printer (int from, int to) this.from = from; this.to = to; public void print () for (int i = from; i <= to; i++) System.out.print (i+"\t"); public class PrinterApp public static void main (String args[]) Printer p1 = new Printer (1,10); Printer p2 = new Printer (11,20); p1.print(); p2.print(); System.out.println ("Fine"); Un programma sequenziale (1) 17 P.Trunfio Un programma sequenziale (2) L esecuzione di PrinterApp genera sempre il seguente output: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 Fine I metodi: p1.print() p2.print() System.out.println( Fine ) sono eseguiti in modo sequenziale. 18 P.Trunfio

class TPrinter extends Thread private int from; private int to; public TPrinter (int from, int to) this.from = from; this.to = to; public void run () for (int i = from; i <= to; i++) System.out.print (i+"\t"); public class TPrinterApp public static void main (String args[]) TPrinter p1 = new TPrinter (1,10); TPrinter p2 = new TPrinter (11,20); p1.start(); p2.start(); System.out.println ("Fine"); Un programma threaded (1) 19 P.Trunfio Un programma threaded (2) L esecuzione di TPrinterApp potrebbe generare il seguente output: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 Fine oppure: Fine 1 2 3 11 4 12 5 13 6 14 7 15 8 16 9 17 10 18 19 20 oppure: 1 2 11 12 3 4 Fine 5 13 14 6 7 15 8 16 17 9 18 19 20 10 e così via: non è possibile fare alcuna assunzione sulla velocità relativa di esecuzione dei thread. L unica certezza è che le operazioni all interno di un dato thread procedono in modo sequenziale. 20 P.Trunfio

Imporre la sequenzialità Per imporre la sequenzialità nell esecuzione dei diversi thread si può fare uso del metodo join(). public class TPrinterApp public static void main (String args[]) TPrinter p1 = new TPrinter (1,10); TPrinter p2 = new TPrinter (11,20); p1.start(); try p1.join(); catch (InterruptedException e) System.out.println(e); p2.start(); try p2.join(); catch (InterruptedException e) System.out.println(e); System.out.println ("Fine"); public class PrinterApp public static void main (String args[]) Printer p1 = new Printer (1,10); Printer p2 = new Printer (11,20); p1.print(); p2.print(); System.out.println ("Fine"); 21 P.Trunfio Somma in concorrenza (1) class Sommatore extends Thread private int da; private int a; private int somma; public Sommatore (int da, int a) this.da = da; this.a = a; public int getsomma() return somma; public void run () somma = 0; for (int i = da; i <= a; i++) somma += i; 22 P.Trunfio

Somma in concorrenza (2) public class Sommatoria public static void main (String args[]) int primo = 1; int ultimo = 100; int intermedio = (primo+ultimo)/2; Sommatore s1 = new Sommatore (primo,intermedio); Sommatore s2 = new Sommatore (intermedio+1,ultimo); s1.start(); s2.start(); try s1.join(); s2.join(); catch (InterruptedException e) System.out.println (e); System.out.println (s1.getsomma()+s2.getsomma()); 23 P.Trunfio Thread con attività ciclica (1) Ciclo finito: public void run () for (int i = 0; i < n; i++) istruzioni Ciclo infinito: public void run () while (true) istruzioni 24 P.Trunfio

Ciclo con terminazione: class mythread extends Thread private boolean continua; public mythread () continua = true; public vodi termina () continua = false; public void run () while (continua) istruzioni Thread con attività ciclica (2) 25 P.Trunfio Una classe Clock (1) class Clock extends Thread private boolean continua; public Clock () continua = true; public void block () continua = false; public void run () int i = 1; while (continua) try sleep (1000); catch (InterruptedException e)system.out.println (e); if (continua) System.out.print ("\n"+i); i++; 26 P.Trunfio

Una classe Clock (2) class ClockController extends Thread private Clock clock; public ClockController (Clock clock) this.clock = clock; public void run () Console.readString ("Press enter to start"); clock.start(); Console.readString ("Press enter to stop"); clock.block(); public class ClockTest public static void main (String args[]) Clock t = new Clock(); new ClockController(t).start(); 27 P.Trunfio Sincronizzazione dei Processi (1 a parte) Introduzione Il problema della sezione critica Soluzioni hardware per la sincronizzazione Semafori 28

Introduzione L accesso concorrente a dati condivisi può portare all inconsistenza dei dati. Per garantire la consistenza dei dati sono necessari meccanismi per assicurare l esecuzione ordinata dei processi cooperanti. 29 Dati condivisi Bounded-Buffer (1) class item... int BUFFER_SIZE = 10; item buffer[] = new item [BUFFER_SIZE]; int in = 0; int out = 0; int counter = 0; 30

Processo produttore Bounded-Buffer (2) item nextproduced; while (true) nextproduced = new item ( ); while (counter == BUFFER_SIZE) ; /* do nothing */ buffer[in] = nextproduced; in = (in + 1) % BUFFER_SIZE; counter++; 31 Bounded-Buffer (3) Processo consumatore item nextconsumed; while (true) while (counter == 0) ; /* do nothing */ nextconsumed = buffer[out]; out = (out + 1) % BUFFER_SIZE; counter--; 32

Bounded-Buffer (4) Le istruzioni: counter++; counter--; devono essere eseguite atomicamente. Una operazione atomica è una operazione che si completa nella sua interezza senza interruzioni. 33 Bounded-Buffer (5) L istruzione counter++ può essere implementata in linguaggio macchina come: register1 = counter register1 = register1 + 1 counter = register1 L istruzione counter-- può essere implementata come: register2 = counter register2 = register2 1 counter = register2 34

Bounded-Buffer (6) Nel caso in cui il produttore ed il consumatore cercassero di aggiornare il buffer contemporaneamente, le istruzioni in linguaggio assembly potrebbero risultare interfogliate (interleaved). L interleaving dipende da come i due processi produttore e consumatore sono schedulati. 35 Bounded-Buffer (7) Si assuma che counter sia inizialmente 5. Un possibile interleaving delle istruzioni è: producer: register1 = counter (register1 = 5) producer: register1 = register1 + 1 (register1 = 6) consumer: register2 = counter (register2 = 5) consumer: register2 = register2 1 (register2 = 4) producer: counter = register1 (counter = 6) consumer: counter = register2 (counter = 4) Il valore di counter potrebbe essere o 4 o 6, mentre il valore corretto dovrebbe essere 5. 36

Race Condition Race condition: la situazione in cui più processi accedono e manipolano dati condivisi in modo concorrente. Il valore finale dei dati condivisi dipende da quale processo ha finito per ultimo. Per eliminare le race condition, i processi concorrenti devono essere sincronizzati. 37 Il problema della sezione critica n processi che competono per usare dati condivisi. Ciascun processo ha un segmento di codice, chiamato sezione critica, nel quale i dati condivisi sono acceduti. Problema: garantire che quando un processo è in esecuzione nella sua sezione critica, nessun altro processo sia autorizzato ad eseguire la propria sezione critica. 38

Soluzione al problema della sezione critica Devono essere soddisfatti i seguenti requisiti: 1) Mutua esclusione. Se il processo P i è in esecuzione nella propria sezione critica, nessuna altro processo può essere in esecuzione nella sua sezione critica. 2) Progresso. se nessun processo è in esecuzione nella propria sezione critica, allora soltanto i processi che cercano di entrare nella sezione critica partecipano alla decisione di chi entrerà davvero, e questa decisione deve avvenire in un tempo finito. 3) Attesa limitata. Deve esistere un limite nel numero di volte che altri processi sono autorizzati ad entrare nelle rispettive sezioni critiche dopo che un processo P i ha fatto richiesta di entrare nella propria sezione critica e prima che quella richiesta sia soddisfatta. Si assume che ciascun processo esegua a velocità non nulla Nessuna assunzione deve essere fatta sulla velocità relativa degli n processi. 39 Sezione critica: soluzioni Soltanto 2 processi, P 0 e P 1 Struttura generale del processo P i while (true) entry section sezione critica exit section sezione non critica I processi possono far uso di variabili condivise per sincronizzare le loro azioni. 40

Algoritmo 1 Variabili condivise: int turn; Inizialmente turn = 0 Quando turn = i P i può entrare in esecuzione nella propria sezione critica Processo P i while (true) while (turn!= i) ; sezione critica turn = j; sezione non critica Soddisfa la condizione della mutua esclusione, ma non quella del progresso (richiede una stretta alternanza dei processi) 41 Algoritmo 2 Variabili condivise: boolean flag[] = new boolean[2]; Inizialmente flag[0] e flag[1] sono false. Quando flag[i] = true P i è pronto ad entrare nella sua sezione critica Processo P i while (true) flag[i] = true; while (flag[j]) ; sezione critica flag[i] = false; sezione non critica Soddisfa la condizione della mutua esclusione, ma non quella del progresso 42

Algoritmo 3 Usa le variabili condivise degli algoritmi 1 e 2. Processo P i while (true) flag[i] = true; turn = j; while (flag[j] && turn == j) ; sezione critica flag[i] = false; sezione non critica Soddisfa tutti e tre i requisiti; tuttavia vale solo per due processi. 43 Algoritmo del Fornaio (1) Sezione critica per n processi: Prima di entrare nella sezione critica, il processo riceve un numero. Il possessore del numero più piccolo entra nella sezione critica. Se i processi P i e P j ricevono lo stesso numero, se i < j, allora P i è servito prima; altrimenti P j è servito prima. Lo schema di numerazione genera sempre numeri in ordine crescente; ad esempio: 1,2,3,3,3,3,4,5... 44

Algoritmo del Fornaio (2) La notazione < indica l ordinamento lessicografico tra coppie del tipo: (ticket #, process id #) (a,b) < (c,d) se a < c oppure a == c && b < d max (a 0,, a n-1 ) è un numero k, tale che k a i per i : 0,, n 1 Dati condivisi boolean choosing[] = new boolean[n]; int number[] = new int [n]; Le strutture dati sono inizializzate a false e 0 rispettivamente 45 Algoritmo del Fornaio (3) while (true) choosing[i] = true; number[i] = max(number[0], number[1],, number [n 1])+1; choosing[i] = false; for (j = 0; j < n; j++) while (choosing[j]) ; while ((number[j]!= 0) && ( (number[j],j) < (number[i],i) ) ; sezione critica number[i] = 0; sezione non critica 46

Soluzioni hardware per la sincronizzazione Controlla e modifica atomicamente il contenuto di una parola. boolean TestAndSet (boolean &target) boolean rv = target; target = true; return rv; 47 Mutua esclusione con Test-and-Set Dati condivisi: boolean lock = false; Processo P i while (true) while (TestAndSet(lock)) ; sezione critica lock = false; sezione non critica 48

Hardware di Sincronizzazione Scambia atomicamente il valore di due variabili. void Swap (boolean &a, boolean &b) boolean temp = a; a = b; b = temp; 49 Mutua Esclusione con Swap Dati condivisi: boolean lock, key; // lock inizializzato a false Processo P i while (true) key = true; while (key == true) Swap(lock,key); sezione critica lock = false; sezione non critica 50

Semafori Strumento di sincronizzazione che in determinate implementazioni non richiede busy waiting. Semaforo S: variabile intera Può essere acceduta esclusivamente attraverso due operazioni indivisibili (atomiche): wait (S): while (S <= 0) ; /* do no-op */ S--; signal (S): S++; 51 Sezione critica di n Processi Dati condivisi: semaforo mutex; // inizialmente mutex = 1 Processo P i : while (true) wait(mutex); sezione critica signal(mutex); sezione non critica; 52

Implementazione dei Semafori Definiamo un semaforo: class Semaforo int value; List listaprocessi = new List (); Assumiamo che esistano le seguenti operazioni: block() sospende il processo che la invoca. wakeup(p) riprende l esecuzione del processo bloccato P. 53 Operazioni associate ai Semafori Supponiamo che sia dichiarato il Semaforo S: wait(s): S.value--; if (S.value < 0) aggiunge questo processo a S.listaProcessi; block(); signal(s): S.value++; if (S.value <= 0) rimuove un processo P da S.listaProcessi; wakeup(p); 54

Semaforo come strumento generale di sincronizzazione Esegue B in P j solo dopo che A esegue in P i Usa il semaforo flag inizializzato a 0 Codice: P i M A signal(flag) P j M wait(flag) B 55 Deadlock e Starvation Deadlock due o più processi sono indefinitamente in attesa per un evento che può essere causato da uno soltanto dei processi in attesa. Siano S e Q due semafori inizializzati a 1 P 0 P 1 wait(s); wait(q); wait(q); wait(s); M M signal(s); signal(q); signal(q) signal(s); Starvation blocking indefinito. Un processo A potrebbe non essere mai rimosso dalla coda del semaforo in cui è sospeso. 56

Due tipi di semafori Semaforo contatore valore intero che può spaziare su un dominio illimitato. Semaforo binario valore intero che può valere solo 0 e 1; può essere più semplice da implementare. E possibile implementare un semaforo contatore usando semafori binari. 57 Sincronizzazione dei Processi (2 a parte) Problemi classici di sincronizzazione Regioni critiche Monitor 58

Problemi classici di sincronizzazione Problema del Bounded-Buffer Problema dei Lettori e degli Scrittori Problema dei Cinque Filosofi 59 Problema del Bounded-Buffer Dati condivisi: Semaforo full, empty, mutex; Inizialmente: full = 0, empty = n, mutex = 1 60

Bounded-Buffer: Processo Produttore while (true) produce un item in nextp wait(empty); wait(mutex); aggiunge nextp al buffer signal(mutex); signal(full); 61 Bounded-Buffer: Processo Consumatore while (true) wait(full); wait(mutex); rimuove un item dal buffer e lo inserisce in nextc signal(mutex); signal(empty); consuma l item in nextc 62

Problema dei Lettori-Scrittori Dati condivisi: Semaforo mutex, wrt; int readcount; Inizialmente: mutex = 1, wrt = 1, readcount = 0 63 Lettori-Scrittori: Processo Scrittore wait(wrt); scrive signal(wrt); 64

Lettori-Scrittori: Processo Lettore wait(mutex); readcount++; if (readcount == 1) wait(wrt); signal(mutex); legge wait(mutex); readcount--; if (readcount == 0) signal(wrt); signal(mutex); 65 Problema dei Cinque Filosofi Dati condivisi: Semaforo chopstick[] = new Semaforo [5]; Inizialmente tutti i semafori valgono 1 66

Problema dei Cinque Filosofi Filosofo i: while (true) wait(chopstick[i]); wait(chopstick[(i+1) % 5]); mangia signal(chopstick[i]); signal(chopstick[(i+1) % 5]); pensa 67 Regioni critiche Costrutto di sincronizzazione di alto livello. Una variabile condivisa v di tipo T, è dichiarata come: v: shared T La variabile v viene acceduta solo dentro uno statement: region v when B do S dove B è una espressione booleana. Mentre lo statement S è in esecuzione, nessun altro processo può accedere la variabile v. 68

Regioni critiche Regioni che fanno riferimento alla stessa variabile condivisa si escludono reciprocamente. Quando un processo prova ad eseguire lo statement della regione critica, l espressione booleana B viene valutata. Se B è true, S viene eseguita. Se è false, il processo viene ritardato fintanto che B diventa true e nessun altro processo si trova nella regione critica associata con v. 69 Dati condivisi: Esempio: Bounded-Buffer class buffer int pool[] = new int[n]; int count, in, out; 70

Bounded-Buffer: Processo Produttore Il processo produttore inserisce nextp nel buffer condiviso: region buffer when (count < n) do pool[in] = nextp; in = (in+1) % n; count++; 71 Bounded-Buffer: Processo Consumatore Il processo consumatore rimuove un item dal buffer condiviso e lo inserisce in nextc: region buffer when (count > 0) do nextc = pool[out]; out = (out+1) % n; count--; 72

Monitor (1) Costrutti di sicronizzazione di alto livello che consentono la condivisione sicura di un tipo di dati astratto tra processi concorrenti. monitor monitor-name dichiarazione di variabili condivise procedure entry P1 ( )... procedure entry P2 ( )... procedure entry Pn ( )... monitor-name ( ) codice di inizializzazione 73 Monitor (2) Per consentire ad un processo di attendere all interno di un monitor, si deve dichiarare una variabile condition: condition x, y; Una variabile condition può essere manipolata solo attraverso le operazioni wait e signal. L operazione x.wait(); sospende il processo che la invoca fino a quando un altro processo non invoca: x.signal(); L operazione x.signal risveglia esattamente un processo. Se nessun processo è sospeso l operazione di signal non ha effetto. 74

Rappresentazione concettuale di un Monitor 75 Monitor di Hoare e di Hansen Supponiamo che un processo P esegua x.signal, ed esista un processo sospeso Q associato alla variabile condition x. Dopo la signal, occorre evitare che P e Q risultino contemporaneamente attivi all interno del monitor: Hansen: Q attende che P lasci il monitor o si metta in attesa su un altra variabile condition. Hoare: P attende che Q lasci il monitor o si metta in attesa su un altra variabile condition. Questa soluzione è preferibile: infatti consentendo al processo P di continuare (soluzione di Hansen), la condizione logica attesa da Q può non valere più nel momento in cui Q viene ripreso. Una soluzione di compromesso: P deve lasciare il monitor nel momento stesso dell esecuzione dell operazione signal, in quanto viene immediatamente ripreso il processo Q. Questo modello è meno potente di quello di Hoare, perchè un processo non può effettuare una signal più di una volta all interno di una stessa procedura. 76

Cinque Filosofi (1) class Filosofi // una classe monitor final int thinking=0, hungry=1, eating=2; int state[] = new int[5]; // gli elementi valgono 0, 1 o 2 condition self[] = new condition[5]; void pickup(int i) // lucidi seguenti void putdown(int i) // lucidi seguenti void test(int i) // lucidi seguenti void init() for (int i = 0; i < 5; i++) state[i] = thinking; 77 Cinque Filosofi (2) void pickup(int i) state[i] = hungry; test(i); if (state[i]!= eating) self[i].wait(); void putdown(int i) state[i] = thinking; // controlla i vicini a destra e a sinistra test((i+4) % 5); test((i+1) % 5); 78

Cinque Filosofi (3) void test(int i) if ( (state[(i + 4) % 5]!= eating) && (state[i] == hungry) && (state[(i + 1) % 5]!= eating) ) state[i] = eating; self[i].signal(); 79 Implementazione dei Monitor (1) Implementazione dei monitor tramite semafori: Variabili: Semaforo mutex; // (inizialmente = 1) Semaforo next; // (inizialmente = 0) int nextcount = 0; Ciascuna procedura atomica F deve essere sostituita da: wait(mutex); corpo di F; if (nextcount > 0) signal(next); else signal(mutex); La mutua esclusione all interno di un monitor è così assicurata. 80

Implementazione dei Monitor (2) Per ciascuna variabile condition x, abbiamo: Semaforo xsem; // (inizialmente = 0) int xcount = 0; L operazione x.wait() può essere implementata come: xcount++; if (nextcount > 0) signal(next); else signal(mutex); wait(xsem); xcount--; 81 Implementazione dei Monitor (3) L operazione x.signal() può essere implementata come: if (xcount > 0) nextcount++; signal(xsem); wait(next); nextcount--; 82

Implementazione dei Monitor (4) Costrutto wait con priorità: x.wait(c); c: espressione intera valutata quando l operazione wait viene eseguita. Il valore di c (un numero di priorità) memorizzato con il nome del processo che è sospeso. Quando viene eseguito x.signal, il processo che ha il più basso numero di priorità viene svegliato. Verifica due condizioni per stabilire la correttezza del sistema: I processi utente devono sempre effettuare le invocazioni sui metodi del monitor secondo una sequenza corretta. Occorre assicurare che i processi non cooperanti non tentino di accedere alle variabili condivise direttamente, bypassando la mutua esclusione fornita dai monitor. 83 Monitor in Java (1) Tutti i thread che fanno parte di una determinata applicazione Java condividono lo stesso spazio di memoria, quindi è possibile che più thread accedano contemporaneamente allo stesso metodo o alla stessa sezione di codice di un oggetto. Per evitare inconsistenze, e garantire meccanismi di mutua esclusione e sincronizzazione, Java supporta la definizione di oggetti monitor. In Java, un monitor è l istanza di una classe che definisce uno o più metodi synchronized. E possibile definire anche soltanto una sezione di codice (ovvero un blocco di istruzioni) come synchronized. In Java ad ogni oggetto è automaticamente associato un lock : per accedere ad un metodo o a una sezione synchronized, un thread deve prima acquisire il lock dell oggetto. Il lock viene automaticamente rilasciato quando il thread esce dal metodo o dalla sezione synchronized, o se viene interrotto da un eccezione. Un thread che non riesce ad acquisire un lock rimane sospeso sulla richiesta della risorsa fino a quando il lock non diventa disponibile. 84 P.Trunfio

Monitor in Java (2) Ad ogni oggetto contenente metodi o sezioni synchronized viene assegnata una sola variabile condition, quindi due thread non possono accedere contemporaneamente a due sezioni synchronized diverse di uno stesso oggetto. L esistenza di una sola variabile condition per ogni oggetto rende il modello Java meno espressivo di un vero monitor, che presuppone la possibilità di definire più sezioni critiche per uno stesso oggetto. 85 P.Trunfio Monitor in Java (3) Ogni oggetto Java fornisce un insieme di metodi di sincronizzazione: void wait() throws InterruptedException: blocca l esecuzione del thread invocante, in attesa che un altro thread invochi i metodi notify() o notifyall() sullo stesso oggetto monitor. Prima di bloccare la propria esecuzione il thread rilascia il lock. void wait(long timeout) throws InterruptedException: è una variante di wait(). Blocca l esecuzione del thread invocante, che viene risvegliato o dall invocazione dei metodi notify() o notifyall(), o quando sia trascorso un tempo pari a timeout millisecondi. L invocazione di wait(0) equivale all invocazione di wait(). void notify(): risveglia un unico thread in attesa sull oggetto monitor corrente. Se più thread sono in attesa, la scelta avviene in maniera arbitraria, dipendente dall implementazione della Java Virtual Machine. Il thread risvegliato compete con ogni altro thread, come di norma, per ottenere la risorsa condivisa. void notifyall(): esattamente come notify(), ma risveglia tutti i thread in attesa sull oggetto corrente. È necessario tutte le volte in cui più thread possano essere sospesi su differenti sezioni critiche dello stesso oggetto (in quanto esiste una unica coda d attesa). 86 P.Trunfio

Bounded Buffer con sincronizzazione Java (1) public class BoundedBuffer private int buffer[]; private int in=0, out=0, count=0, dim; public BoundedBuffer (int dim) this.dim=dim; buffer=new int[dim]; public synchronized int get () /* lucidi seguenti */ public synchronized void put (int item) /* lucidi seguenti */ 87 Bounded Buffer con sincronizzazione Java (2) public synchronized int get () while (count==0) try wait(); catch (InterruptedException e) int item = buffer[out]; out=(out+1)%dim; count--; notifyall(); return item; 88

Bounded Buffer con sincronizzazione Java (3) public synchronized void put (int item) while (count==dim) try wait(); catch (InterruptedException e) buffer[in] = item; in=(in+1)%dim; count++; notifyall(); 89 Bounded Buffer con sincronizzazione Java (4) public class Consumer extends Thread private BoundedBuffer buffer; private int id; public Consumer (BoundedBuffer buffer, int id) this.buffer = buffer; this.id = id; public void run () for (int i=0; i<10; i++) int value = buffer.get(); System.out.println ("Consumer #"+id+" ha estratto "+value); 90

Bounded Buffer con sincronizzazione Java (5) public class Producer extends Thread private BoundedBuffer buffer; private int id; public Producer (BoundedBuffer buffer, int id) this.buffer = buffer; this.id = id; public void run () for (int i=0; i<10; i++) buffer.put(i); System.out.println ("Producer #"+id+" ha inserito "+i); 91 Bounded Buffer con sincronizzazione Java (6) public class BBTest public static void main (String args[]) BoundedBuffer bb = new BoundedBuffer (10); Producer pv[] = new Producer [5]; for (int i = 0; i < 5; i++) pv[i] = new Producer (bb,i); pv[i].start(); Consumer cv[] = new Consumer [9]; for (int i = 0; i < 9; i++) cv[i] = new Consumer (bb,i); cv[i].start(); 92

Lettori-Scrittori con sincronizzazione Java (1) public class Database private int readercount; private boolean dbreading; private boolean dbwriting; public Database() readercount = 0; dbreading = false; dbwriting = false; public synchronized int startread() /* lucidi seguenti */ public synchronized int endread() /* lucidi seguenti */ public synchronized void startwrite() /* lucidi seguenti */ public synchronized void endwrite() /* lucidi seguenti */ 93 Lettori-Scrittori con sincronizzazione Java (2) public synchronized int startread() while (dbwriting == true) try wait(); catch (InterruptedException e) ++readercount; if (readercount == 1) dbreading = true; return readercount; 94

Lettori-Scrittori con sincronizzazione Java (3) public synchronized int endread() --readercount; if (readercount == 0) dbreading=false; notifyall(); return readercount; 95 Lettori-Scrittori con sincronizzazione Java (4) public synchronized void startwrite() while (dbreading == true dbwriting == true) try wait(); catch (InterruptedException e) dbwriting = true; public synchronized void endwrite() dbwriting = false; notifyall(); 96

Blocchi sincronizzati (1) Anche blocchi di codice, oltre che interi metodi, possono essere dichiarati synchronized. Ciò consente di associare un lock la cui durata è tipicamente inferiore a quella di un intero metodo synchronized. 97 Blocchi sincronizzati (2) public void syncronized F() // sezione non critica (p.es.: inizializzazione di variabili locali) // sezione critica // sezione non critica public void F() // sezione non critica synchronized (this) // sezione critica // sezione non critica 98

Il problema dello Sleeping Barber E dato un salone di barbiere, avente un certo numero di posti d attesa ed un unica poltrona di lavoro. Nel salone lavora un solo barbiere, il quale è solito addormentarsi sulla poltrona di lavoro in assenza di clienti. Arrivando nel salone, un cliente può trovare le seguenti situazioni: Il barbiere dorme sulla poltrona di lavoro. Il cliente sveglia il barbiere e si accomoda sulla poltrona di lavoro, quindi il barbiere lo serve. Il barbiere sta servendo un altro cliente: se ci sono posti d attesa liberi, il cliente attende, altrimenti se ne va. Scrivere in Java un programma che risolva tale problema, simulando l attività dei diversi soggetti (il Salone, il Barbiere, i Clienti) ed evidenziandone su video lo stato. L implementazione della soluzione deve far uso delle opportune primitive di sincronizzazione e mutua esclusione. 99 Il problema dello Sleeping Barber public class SleepingBarber public static void main (String args[]) int postidiattesa=5; Salone s = new Salone (postidiattesa); Barbiere b = new Barbiere (s); b.start(); for (int i = 1; i <= 10; i++) Cliente c = new Cliente (s, i); c.start (); class Barbiere extends Thread class Cliente extends Thread class Salone 100

class Barbiere extends Thread private Salone salone; public Barbiere (Salone salone) this.salone = salone; public void run () while (true) salone.servizio(); Barbiere 101 Cliente (1) class Cliente extends Thread private Salone salone; private int id; public Cliente (Salone salone, int id) this.salone = salone; this.id = id; public void run () while (true) int tempodiricrescita = 1000+(int)(Math.random()*3000); System.out.println ("Il cliente "+id+" attende la ricrescita dei capelli. "+ "Tempo di ricrescita = "+tempodiricrescita); try sleep (tempodiricrescita); catch (InterruptedException e) System.out.println (e); 102

Cliente (2) int tempodiservizio = salone.entrasalone(id); if (tempodiservizio!= -1) try sleep (tempodiservizio); catch (InterruptedException e) System.out.println (e); salone.lasciasalone(id); // while 103 Salone (1) class Salone private int posti; // numero di posti d attesa private int postioccupati; // numero di posti d attesa occupati private int clientidaservire; private boolean barbieredorme; private boolean poltronaoccupatadacliente; public Salone (int postiattesa) posti = postiattesa; postioccupati = 0; clientidaservire = 0; barbieredorme = true; poltronaoccupatadacliente = false; public synchronized int entrasalone (int idcliente) public synchronized void lasciasalone (int idcliente) public synchronized void servizio () 104

Salone: entrasalone (1) public synchronized int entrasalone (int idcliente) System.out.println ("Il cliente "+idcliente+" entra nel salone"); if (postioccupati == posti) System.out.println ("Il cliente "+idcliente+" non trova posto ed "+ "abbandona il salone senza essere servito"); return -1; clientidaservire++; if (!barbieredorme) // il barbiere è occupato, ma ci sono posti liberi System.out.println ("Il cliente "+idcliente+" si mette in attesa"); System.out.println ("Posti di attesa occupati = "+(++postioccupati)); do try wait(); catch (InterruptedException e) System.out.println (e); while (poltronaoccupatadacliente); System.out.println ("Posti di attesa occupati = "+(--postioccupati)); 105 Salone: entrasalone (2) else // il salone è vuoto System.out.println ("Il cliente "+idcliente+" sveglia il barbiere"); notify(); // il cliente sveglia il barbiere poltronaoccupatadacliente = true; // il cliente occupa la poltrona int tempodiservizio = 1000+(int)(Math.random()*4000); System.out.println ("Il cliente "+idcliente+" inizia ad essere servito. "+ "Tempo di servizio = "+tempodiservizio+" ms"); return tempodiservizio; 106

Salone: lasciasalone public synchronized void lasciasalone (int idcliente) System.out.println ("Il cliente "+idcliente+" e' stato servito e lascia il salone"); clientidaservire--; poltronaoccupatadacliente = false; System.out.println ("Posti di attesa occupati = "+postioccupati); if (postioccupati > 0) System.out.println ("Si svegliano i clienti in attesa"); notifyall(); 107 Salone: servizio public synchronized void servizio () if (clientidaservire == 0) System.out.println("Il barbiere approfitta dell'assenza di clienti per dormire"); barbieredorme = true; try wait(); catch (InterruptedException e) System.out.println (e); barbieredorme = false; 108

Semafori in Java (1) Java non fornisce semafori, ma una classe semaforo può essere costruita usando i meccanismi di sincronizzazione di Java: public class Semaphore private int value; public Semaphore() value = 0; public Semaphore(int v) value = v; public synchronized void P () /* lucido seguente */ public synchronized void V () /* lucido seguente */ 109 Semafori in Java (2) public synchronized void P () if (--value < 0) try wait(); catch (InterruptedException e) public synchronized void V () if (++value <= 0) notify (); 110

Lettori-Scrittori con semafori Java (1) public class Reader extends Thread private Database server; public Reader (Database db) server = db; public void run () int c; while (true) c = server.startread(); /* lettura dal database */ c = server.endread(); 111 Lettori-Scrittori con semafori Java (2) public class Writer extends Thread private Database server; public Writer (Database db) server = db; public void run () while (true) server.startwrite(); /* scrittura sul database */ server.endwrite(); 112

Lettori-Scrittori con semafori Java (3) public class Database private int readercount; // numero di lettori attivi private Semaphore mutex; // per l accesso a readercount private Semaphore db; // per l accesso al database public Database () readercount = 0; mutex = new Semaphore(1); db = new Semaphore(1); public int startread() /* lucidi seguenti */ public int endread() /* lucidi seguenti */ public void startwrite() /* lucidi seguenti */ public void endwrite() /* lucidi seguenti */ 113 Lettori-Scrittori con semafori Java (4) public int startread () mutex.p(); ++readercount; if (readercount == 1) db.p(); mutex.v(); return readercount; 114

Lettori-Scrittori con semafori Java (5) public int endread() mutex.p(); --readercount; if (readercount == 0) db.v(); mutex.v(); return readercount; 115 Lettori-Scrittori con semafori Java (6) public void startwrite() db.p(); public void endwrite() db.v(); 116

Cinque Filosofi con semafori Java (1) public class Filosofo extends Thread private int id; private Semaphore destro, sinistro; public Filosofo (int id, Semaforo dx, Semaforo sx) this.id=id; destro=dx; sinistro=sx; public void run () while (true) /* pensa */ destra.p(); sinistra.p(); /* mangia */ destra.v(); sinistra.v(); 117 Cinque Filosofi con semafori Java (2) public class CinqueFilosofi public static void main (String args[]) Semaphore sv[] = new Semaphore[5]; for (int i=0; i < 5; i++) sv[i]=new Semaphore(1); Filosofo fv[] = new Filosofo[5]; for (int i=0; i < 5; i++) fv[i]=new Filosofo(i,sv[(i+4)%5], sv[(i+1)%5]); for (int i=0; i < 5; i++) fv[i].start(); 118

Comunicazione tra processi (IPC) Meccanismo per la comunicazione e la sincronizzazione dei processi. Sistema basato sullo scambio di messaggi: i processi comunicano tra loro senza far uso di variabili condivise. IPC fornisce due operazioni: send(message) la dimensione del messaggio può essere fissa o variabile receive(message) Se P e Q vogliono comunicare, devono: Stabilire un canale di comunicazione tra loro Scambiare messaggi attraverso send/receive Implementazione del canale di comunicazione: fisica (ad. es., memoria condivisa, bus hardware) logica (ad es., proprietà logiche) 119 Problemi implementativi (1) Per effettuare l implementazione è necessario sapere: Come vengono stabiliti i canali. Se un canale può essere associato a più di due processi. Quanti canali possono esistere tra ogni coppia di processi. Che cosa si intende per capacità di un canale, cioè se il canale dispone di spazio buffer, e in caso ne disponga occorre conoscere la dimensione di questo spazio. Che cosa si intende per dimensione dei messaggi. Occorre sapere se il canale può supportare messaggi con dimensione variabile o messaggi con dimensione fissa. Se un canale è unidirezionale o bidirezionale, e cioè se i messaggi possono fluire soltanto in una direzione, come per esempio da P a Q, oppure in entrambe le direzioni. 120

Problemi implementativi (2) Inoltre esistono diversi metodi per effettuare l implementazione logica di un canale e delle operazioni send/receive: Comunicazione diretta o indiretta. Comunicazione simmetrica o asimmetrica. Buffering automatico o esplicito. Invio per copia o per riferimento. Messaggi con dimensione fissa o dimensione variabile. 121 Nominazione I processi che vogliono comunicare devono disporre di un modo con cui riferirsi agli altri processi; a tale scopo è possibile utilizzare: Comunicazione diretta. Comunicazione indiretta. 122

Comunicazione diretta (1) I processi devono indicare esplicitamente il nome del proprio interlocutore: send (P, message) Invia message al processo P. receive(q, message) Riceve, in message, un messaggio dal processo Q. All interno di questo schema, un canale di comunicazione ha le seguenti caratteristiche: Tra ogni coppia di processi che intendono comunicare viene stabilito automaticamente un canale. Per comunicare i processi devono conoscere solo le reciproche identità. Un canale è associato esattamente a due processi. Tra ogni coppia di processi comunicanti c è esattamente un canale. Il canale può essere unidirezionale, ma usualmente è bidirezionale. 123 Comunicazione diretta (2) Esempio: una soluzione al problema del produttore-consumatore: Produttore: while (true) genera un elemento in nextp send (consumatore, nextp); Consumatore: while (true) receive (produttore, nextc); consuma l elemento nextc 124

Comunicazione diretta (3) Nello schema precedente si usa una simmetria nell indirizzamento, cioè per poter comunicare il trasmittente ed il ricevente devono nominarsi a vicenda. Una variante di questo schema utilizza un indirizzamento asimmetrico: send(p,message) Invia message al processo P. receive(id,message) Riceve, in message, un messaggio da qualsiasi processo; nella variabile id viene riportato il nome del processo con il quale ha avuto luogo la comunicazione. 125 Comunicazione indiretta (1) I messaggi vengono inviati a dei mailbox (chiamati anche porte). Ciascun mailbox è caratterizzato da un id univoco. I processi possono comunicare solo se condividono un mailbox. Le primitive send e receive sono definite come segue: send(a, message) Invia message al mailbox A. receive(a, message) Riceve, in message, un messaggio dal mailbox A. 126

Comunicazione indiretta (2) In questo schema le proprietà del canale di comunicazione sono le seguenti: Tra una coppia di processi viene stabilito un canale solo se i processi hanno un mailbox in comune. Un canale può essere associato a piu di due processi. Tra ogni coppia di processi comunicanti può esserci un certo numero di canali diversi, ciascuno corrispondente a un mailbox. Un canale può essere unidirezionale o bidirezionale. Il sistema operativo offre un meccanismo che permette ad un processo le seguenti operazioni: Creare un nuovo mailbox. Inviare e ricevere messaggi tramite il mailbox. Distruggere un mailbox. 127 Comunicazione indiretta (3) Si supponga che i processi P1, P2 e P3 condividano il mailbox A. Il processo P1 invia un messaggio ad A, mentre sia P2 che P3 eseguono una receive da A. Quale processo riceverà il messaggio? Tale problema può essere affrontato adottando una delle seguenti soluzioni: E possibile fare in modo che un canale sia associato al massimo a due processi. E possibile consentire ad un solo processo alla volta di eseguire una operazione receive. E possibile consentire al sistema di decidere arbitrariamente quale processo riceverà il messaggio (il messaggio sarà ricevuto da P2 o da P3, ma non da entrambi). Il sistema può comunicare l identità del ricevente al trasmittente 128

Comunicazione indiretta (4) Un mailbox può appartenere al processo o al sistema: Nel caso appartenga a un processo, il mailbox è associato al processo definito come parte di esso. Un mailbox posseduto dal sistema operativo ha sua una esistenza propria: è indipendente e non è legato ad alcun processo particolare. 129 Comunicazione indiretta (5) Il processo che crea un nuovo mailbox è per default il proprietario del mailbox. Inizialmente, il proprietario è l unico processo in grado di ricevere messaggi attraverso questo mailbox. Tuttavia, il diritto di proprietà e il privilegio di ricezione possono essere passati ad altri processi per mezzo di idonee system call. I processi possono anche condividere una mailbox tramite la funzione di creazione di un processo. Quando un mailbox condiviso non è più utilizzato da alcun processo, è necessario deallocare lo spazio di memoria ad esso associato. Tale operazione è delegata ad un garbage collector gestito dal sistema operativo. 130

Sincronizzazione Lo scambio di messaggi può essere bloccante o non bloccante: Se è bloccante, lo scambio di messaggi è considerato sincrono. Se è non bloccante, lo scambio di messaggi è considerato asincrono. Le primitive send e receive possono essere bloccanti o non bloccanti. 131 Buffering (1) Un canale ha una capacità che determina il numero dei messaggi che possono risiedere temporaneamente al suo interno. Questa caratteristica può essere immaginata come una coda di messaggi legata al canale. Fondamentalmente esistono tre modi per implementare questa coda: Capacità zero. Il mittende deve aspettare che il ricevitore sia effettivamente pronto a ricevere un messaggio. Capacità limitata. Il mittente deve attendere se il canale di comunicazione è saturo. Capacità illimitata. Il mittente non attende mai. 132

Buffering (2) Capacità zero: La coda ha lunghezza massima 0, quindi il canale non può avere messaggi in attesa al suo interno. In questo caso il trasmittente deve attendere che il ricevente abbia ricevuto il messaggio. Affinchè il trasferimento di messaggi abbia luogo, i due processi devono essere sincronizzati. Questo tipo di sincronizzazione è chiamato rendez-vous. 133 Buffering (3) Capacità limitata: La coda ha lunghezza finita n, quindi al suo interno possono risiedere al massimo n messaggi. Se la coda non è piena quando viene inviato un nuovo messaggio, quest ultimo viene posto in fondo alla coda, il messaggio viene copiato oppure viene tenuto un puntatore a quel messaggio. Il trasmittente può proseguire la propria esecuzione senza essere costretto ad attendere. Ma il canale ha comunque una capacità limitata. Se il canale è pieno, il trasmittente deve attendere che nella coda ci sia spazio disponibile. 134

Buffering (4) Capacità non limitata: La coda ha potenzialmente lunghezza infinita, quindi al suo interno può essere in attesa un numero indeterminato di messaggi. Il trasmittente non resta mai in attesa. 135 Buffering (5) Nel caso in cui la capacità è diversa da zero, un processo non è in grado di sapere se al termine dell operazione send il messaggio sia arrivato alla sua destinazione. Se questa informazione è indispensabile ai fini del calcolo, il trasmittente deve comunicare esplicitamente con il ricevente per sapere se quest ultimo abbia ricevuto il messaggio. Si supponga che il processo P invii un messaggio al processo Q e possa continuare la propria esecuzione solo dopo che questo messaggio sia stato ricevuto. Il processo P esegue la sequenza: Il processo Q esegue la sequenza: send (Q, message); receive (Q, message); receive (P, message); send (P, acknowledgement ); 136

Condizioni di eccezione (1) Un sistema di messaggi è utilizzato soprattutto in un sistema distribuito. In presenza di un guasto è necesario un ripristino dall errore (gestione delle condizioni di eccezione). Le condizioni d errore più frequenti sono: Terminazione del processo. Messaggi perduti. Messaggi alterati 137 Terminazione del processo. Condizioni di eccezione (2) Un messaggio trasmittente o ricevente può terminare prima che un messaggio sia stato elaborato. Questa situazione comporta la presenza di messaggi che non vengono mai ricevuti o di processi che restano in attesa di messaggi che non verranno mai inviati. In particolare possono verificarsi i seguenti casi: Un processo ricevente P attende un messaggio del processo Q che ha terminato. Se non viene intrapresa alcuna azione P rimane bloccato per sempre. In questo caso, il sistema può terminare l esecuzione di P, oppure informare P che Q ha terminato. Il processo P invia un messaggio al processo Q che ha terminato. P continua semplicemente la propria esecuzione. Per sapere se il messaggio sia stato elaborato da Q, P deve essere esplicitamente programmato per la ricezione di un acknowledgement. Nel caso senza buffering ci sono due soluzioni: il sistema può terminare l esecuzione di P, oppure informare P che Q ha terminato. 138