Università degli Studi della Calabria Corso di Laurea in Ingegneria Informatica A.A. 2001/2002 Corsi A e B Esercitazioni 3 e 4 Sincronizzazione dei Processi (2 a parte) Problemi classici di sincronizzazione Regioni critiche Monitor
Problemi classici di sincronizzazione Problema del Bounded-Buffer Problema dei Lettori e degli Scrittori Problema dei Cinque Filosofi Problema del Bounded-Buffer Dati condivisi: Semaforo full, empty, mutex; Inizialmente: full = 0, empty = n, mutex = 1
Bounded-Buffer: Processo Produttore while (true) { produce un item in nextp wait(empty); wait(mutex); aggiunge nextp al buffer signal(mutex); signal(full); 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
Problema dei Lettori-Scrittori Dati condivisi: Semaforo mutex, wrt; int readcount; Inizialmente: mutex = 1, wrt = 1, readcount = 0 Lettori-Scrittori: Processo Scrittore wait(wrt); scrive signal(wrt);
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); Problema dei Cinque Filosofi Dati condivisi: Semaforo chopstick[] = new Semaforo [5]; Inizialmente tutti i semafori valgono 1
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 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.
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. Dati condivisi: Esempio: Bounded-Buffer class buffer { int pool[] = new int[n]; int count, in, out;
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++; 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--;
Implementazione di region x when B do S (1) Associamo alla variabile condivisa x, le seguenti variabili: Semaforo mutex, firstdelay, seconddelay; int firstcount, secondcount; L accesso in mutua escusione alla sezione critica è garantito da mutex. Se un processo non può entrare nella sezione critica perchè il valore dell espressione booleana B è false, esso aspetta prima sulsemaforo firstdelay; successivamente viene spostato sul semaforo seconddelay prima che possa rivalutare B. Implementazione di region x when B do S (2) firstcount e secondcount tengono conto rispettivamente del numero di processi in attesa sui semafori firstdelay e seconddelay. L algoritmo assume un ordinamento FIFO nell accodamento dei processi nei semafori. Per una modalità di accodamento arbitraria è necessaria una implementazione più complessa.
Monitor 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 () {... { codice di inizializzazione Monitor 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.
Rappresentazione concettuale di un Monitor Monitor con variabili condition
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; 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);
Cinque Filosofi (3) void test(inti) { if ( (state[(i + 4) % 5]!= eating) && (state[i] == hungry) && (state[(i + 1) % 5]!= eating) ) { state[i] = eating; self[i].signal(); 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.
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--; Implementazione dei Monitor (3) L operazione x.signal() può essere implementata come: if (xcount > 0) { nextcount++; signal(xsem); wait(next); nextcount--;
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.