Esercizi sulla sincronizzazione Problema dei lettori e degli scrittori Semafori: Lettori scrittori con priorità agli scrittori Monitor: Produttori consumatori Il conto bancario cointestato A cena con i cannibali Variabili condizione Tre a uno Processo lettore sem_wait(mutex); numlettori++; if (numlettori == 1) sem_signal(mutex); si effettua la lettura sem_wait(mutex); numlettori ; if (numlettori == 0) sem_signal(mutex); Processo scrittore si effettua la scrittura Priorità dei lettori 6.1 6.2 Lettori scrittori con priorità agli scrittori Ogni lettore che vuole leggere è accettato, a meno che uno scrittore chiede di entrare: in questo caso viene inibito l'ingresso ai lettori successivi; Un solo lettore inibisce gli scrittori e il primo degli scrittori che entra inibisce ogni altra lettura e scrittura. Lo scrittore deve utilizzare: un semaforo lettura (inizialmente 1) che blocca i lettori mentre gli scrittori accedono alla risorsa una variabile numscrittori che controlla il semaforo lettura un semaforo mutex1 (inizialmente 1) che controlla l'accesso alla variabile numscrittori un semaforo scrittura (inizialmente 1) che assicura l'accesso esclusivo in scrittura alla risorsa. Processo scrittore numscrittori++; if (numscrittori == 1) sem_wait(lettura); si effettua la scrittura numscrittori ; if (numscrittori == 0) sem_signal(lettura); 6.3 6.4 Lettori scrittori con priorità agli scrittori Ogni lettore che vuole leggere è accettato, a meno che uno scrittore chiede di entrare: in questo caso viene inibito l'ingresso ai lettori successivi; Un solo lettore inibisce gli scrittori e il primo degli scrittori che entra inibisce ogni altra lettura e scrittura. Il lettore deve utilizzare: un semaforo unoallavolta (inizialmente 1) che permette ad un solo lettore di accodarsi su lettura una variabile numlettori che controlla il semaforo scrittura un semaforo mutex2 (inizialmente 1) che controlla l'accesso alla variabile numlettori Processo scrittore numscrittori++; if (numscrittori == 1) sem_wait(lettura); si effettua la scrittura numscrittori ; if (numscrittori == 0) sem_signal(lettura); Processo lettore sem_wait(unoallavolta); sem_wait(lettura); sem_wait(mutex2); numlettori++; if (numlettori == 1) sem_signal(mutex2); sem_signal(lettura); sem_signal(unoallavolta); si effettua la lettura sem_wait(mutex2); numlettori--; if (numlettori == 0) sem_signal(mutex2); 6.5 6.6
Produttori consumatori Il conto bancario cointestato var buffer: array[0..n-1] of item; in, out: 0..n-1; size: 0..n; empty, full: condition; Procedure body Consumatori (var x:item); if size = 0 then empty.wait(); x := buffer[out]; out:=(out + 1) mod n; size:=size-1; full.signal(); Procedure body Produttori (x: item); if size = n then full.wait(); buffer[in]:=x; in:=(in+1) mod n; size:=size+1; empty.signal(); size:=0; in:=0; out:=0; end Un conto bancario è condiviso tra più persone. Ciascuna può depositare o prelevare soldi dal conto, ma il saldo non deve mai diventare negativo. Se una persona vuole prelevare e ci sono soldi a sufficienza, lo fa aggiornando il saldo, altrimenti resta in attesa che il saldo renda possibile il prelievo e, non appena c è stato un deposito: avverte le eventuali altre persone in attesa e cerca di effettuare il prelievo. Quando una persona deposita, avverte un altra eventuale persona in attesa che c è stato un deposito. Scrivere un monitor con le funzioni prelievo e deposito per risolvere questo problema. 6.7 6.8 Dati locali: var saldo: integer; sosp: condition; n-sosp: integer; Procedure body prelievo(cifra: integer) var presi: boolean; presi := false; repeat if saldo > cifra: then saldo := saldo - cifra; presi := true; else n-sosp := n-sosp + 1; sosp.wait(); if n-sosp > 0 then n-sosp := n-sosp -1; sosp.signal(); until presi; Il conto bancario cointestato 6.9 Procedure body deposito(cifra: integer) saldo := saldo + cifra; if n-sosp > 0 then n-sosp := n-sosp -1; sosp.signal(); saldo := 0; n-sosp := 0; A cena con i cannibali Una tribù di N cannibali mangia in comune da una pentola che può contenere fino a M (M < N) porzioni di stufato di missionario. Quando un cannibale ha fame controlla la pentola: Se non ci sono porzioni, sveglia il cuoco ed aspetta che questo abbia riempito di nuovo la pentola. Se la pentola contiene almeno un pasto, si mette in attesa di essere servito. Il cuoco controlla inizialmente che ci siano delle porzioni: Se ci sono si addormenta, altrimenti, cuoce M porzioni e le serve ai cannibali in attesa. Si descriva una soluzione che utilizzi un monitor e si delineino le procedure riempi e servi. 6.10 A cena con i cannibali La struttura dei processi cannibale e cuoco è la seguente: repeat serviti <mangia> until false end Scrivere un monitor (con procedure serviti e riempi) che controlli tale interazione. Il cuoco deve essere svegliato solo quando la pentola è vuota. 6.11 repeat riempi until false end Dati locali: var pasti: 0..M; #pasti cuoco_addormentato: boolean; cannibale_affamato: integer; cannibale, cuoco: condition; Procedure entry serviti() if pasti = 0 then if cuoco_addormentato then cuoco_addormentato := false; cuoco.signal(); else cannibale_affamato++; cannibale.wait(); pasti := pasti -I; A cena con i cannibali 6.12 Procedure entry riempi() if pasti > 0 then cuoco_addormentato := true; cuoco.wait(); pasti := M; while cannibale_affamato > 0 do cannibale_affamato- -; cannibale.signal(); if pasti = 0 then pasti := M; pasti := 0; cuoco_addormentato := false; cannibale_affamato := 0;
Variabili condizione La variabili condizione permettono ad un thread di attendere l occorrenza di un evento. Quando un altro thread causerà l occorrenza di tale evento, uno o più thread in attesa riceveranno un segnale e si risveglieranno. mutex unlocked? Y condition met? Y do work unlock mutex Variabili condizione La variabili condizione hanno tre componenti: la variabile condizione, un mutex associato, un predicato. Il programmatore ha il compito di di definire tutte e tre le componenti: Il predicato è la condizione (o il valore) che un thread controllerà per determinare se deve attendere; il mutex è il meccanismo che protegge il predicato; la variabile condizione è il meccanismo con cui il thread attende il verificarsi della condizione. block until unlocked N The mutex is reacquired after the condition is signaled. This way, when the predicate is evaluated, it will have a consistent state. N wait/unlock mutex lock mutex This is an atomic operation, meaning that a thread cannot be context switched in the middle of this operation. The condition is signaled here. 3.13 3.14 Attendere una condizione Segnalare una condizione Un thread può attendere su una variabile condizione per un tempo specificato o indefinitamente. Queste funzioni rilasciano il mutex associato prima di bloccare e lo riacquisiscono prima di ritornare, permettendo l accesso al mutex da altri thread. Quando la condizione si verifica, si può risvegliare almeno un thread in attesa con pthread_cond_signal() Se si vogliono risvegliare tutti i thread in attesa si utilizza pthread_cond_broadcast() pthread_cond_timedwait() If the condition is not signaled in Xamount of time, return an error. pthread_cond_wait() or wait indefinately pthread_cond_signal() pthread_cond_broadcast() wake up one thread wake up all threads Threads waiting on a condition variable: 3.15 3. Esempio Esempio Si considerino due variabili condivise x e y, protette da un mutex mut e una variabile condivisa cond che viene segnalata ogni volta che x > y int x,y; pthread_mutex_t mut = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t cond = PTHREAD_COND_INITIALIZER; Per aspettare fino a che x è più grande di y si usa: while (x <= y) pthread_cond_wait(&cond, &mut); /* operate on x and y */ Modifiche di x e y che possono rendere x più grande di y devono segnalare la condizione, se necessario: /* modify x and y */ if (x > y) pthread_cond_broadcast(&cond); 3.17 3.18
Esempio Modifiche di x e y che possono rendere x più grande di y devono segnalare la condizione, se necessario: /* modify x and y */ if (x > y) pthread_cond_broadcast(&cond); Per aspettare fino a che x è più grande di y si usa: while (x <= y) pthread_cond_wait(&cond, &mut); /* operate on x and y */ #include <pthread.h> #include <stdio.h> Esempio #include <unistd.h> #include <errno.h> #include <stdlib.h> extern void fatal_error(int err_num, char *func); #define check_error(return_val, msg) \ if (return_val!= 0) fatal_error(return_val, msg); /* Creazione di due thread che: - dopo un intervallo di tempo casuale, - aggiornano metà elementi di un array condiviso con il proprio pid. Si impone un lock in modo da avere - l'accesso esclusivo all array - l'aggiornamento dell'indice dell'array Si usa una variabile condizione per avere: - una alternanza che faccia entrare il primo tre volte di seguito, il secondo una*/ /* la variabile da condividere e' un vettore di pid */ int condivisa[] = 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0; int ncondivisa, cont; /* il lock per regolare l'accesso alla memoria condivisa */ pthread_mutex_t CondMutex = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t Cond = PTHREAD_COND_INITIALIZER; 3.19 /* aggiornatore casuale */ void *primo ( int dim); 3.20 void secondo ( int dim); int main() pthread_t tid1,tid2; int retcode, k, dim; dim=sizeof(condivisa)/sizeof(int); ncondivisa=0; cont=0; retcode=pthread_create(&tid1,null,(void *(*)()) primo,(void *)dim); check_error(retcode, create failed"); retcode=pthread_create(&tid2,null,(void *(*)())secondo,(void*)dim); check_error(retcode, create failed"); /* attende la terminazione di entrambi i thread */ retcode = pthread_join(tid1,null); check_error(retcode, join failed"); retcode = pthread_join(tid2,null); check_error(retcode, join failed"); for(k=0; k<dim; k++) printf("condivisa[%d]=%d\n", k,condivisa[k]); exit(0); void * primo (int dim) 3.21 exit(0); void * primo (int dim) int sl; int test=1; srand(getpid()); while( test ) sl = 1+(int) (3.0*rand()/(RAND_MAX+1.0)); pthread_mutex_lock( &CondMutex ); if ( ncondivisa >= dim ) test=0; else sl = 1+(int) (2.0*rand()/(RAND_MAX+1.0)); condivisa[ncondivisa]=getpid(); ncondivisa++; cont++; if ( cont > 2 ) pthread_cond_signal ( &Cond ); pthread_mutex_unlock( &CondMutex ); return( (void *) NULL); void * secondo (int dim) 3.22 return( (void *) NULL); void * secondo (int dim) int sl; int test=1; srand(getpid()); while( test ) sl = 1+(int) (3.0*rand()/(RAND_MAX+1.0)); Vol. 9 n. 1 Jan. 1966 pthread_mutex_lock( &CondMutex ); if ( ncondivisa >= dim ) test=0; else while (!( cont > 2) ) pthread_cond_wait (&Cond, &CondMutex); cont=0; sl = 1+(int) (2.0*rand()/(RAND_MAX+1.0)); condivisa[ncondivisa]=getpid(); ncondivisa++; pthread_mutex_unlock( &CondMutex ); return( (void *) NULL); 3.23 6.24
flag [ i ] := true; while ( turn!= i ) while ( flag [ 1-i ] ); turn := i flag [ i ] := false; while(1); Perché non funziona? (Problema tratto da Stallings p. 267) while(1); while(1); 1 26 3 4 5 6.25 6.26 while(1); 8 9 10 while(1); 7 while(1); 10 while(1); 7 11 6.27 6.28 while(1); 10 while(1); 12 while(1); 13 while(1); 12 flag [1] = false flag [1] = false 6.29 6.30
while(1); 13 while(1); 14 12 while(1); 13 while(1); 14 15 flag [1] = false 6.31 6.32 while(1); 17 while(1); while(1); 17 while(1); turn = 0 6.33 6.34 while(1); 19 18 20 while(1); while(1); while(1); turn = 0 6.35 6.36