LEZIONE 5
PROBLEMI “CLASSICI”
DELLA PROGRAMMAZIONE
CONCORRENTE:
PRODUTTORI/CONSUMATORI
IN QUESTA LEZIONE IMPAREREMO...
le caratteristiche del problema produttori/consumatori
| risolvere il problema produttori/consumatori
Generalita
Fsiste un certo numero di problemi “elassici” della programmazione concorrente che rappresene
tano la casistica completa delle diverse situazioni di concorrenza; possono essere suddivisi in tre
‘gruppi, dei quali riportiamo il nome con il quale sono indicati nella letteratura informatica:
» produttore/eonsumatore (produeer/eonsumer)
= I produtiore= 1 consumatore
~ butier limitato o circolare (bounded buffer)
=» produtort ~n consumator!
letoori e soritteri (readers/writers)
? filosofi a cena (dining philosophers)
In questa lezione descriveremo il primo dei tre problemi nelle sue diverse situazioni mentre riman-
diamo gh altri alle successive lezioni
@ Produttore/consumatore
I problema del produttore/consumatore & un modello classico per lo studio delle sincronizzazione
nei sistemi operativi dove piit processi devono coordinarsi per scambiarsi delle informazioni
Un produttore ¢ un consumatore
Nella sua formulazione pit: semplice sono presenti due process, il primo, detto produttore, che
vuole inviare informazioni scrivendo un dato (o un messaggio) in un’area di memoria condivisa (neinostri esempi in una varishile) e il secondo,
chiamato consumatore, che vuole prelevare
i valori prodotti dal primo e “consumarli”. >
Un tipico esempio & la gestione della stampa, D Butler
dove vengono inviati i dati al processo che si
oveupa di stamparh.
I sistema @ soggetto ai seguenti vincoli:
» il produttore pud derositare un dato alla voltae non pud serivere un nuovo messadgio se il consti=
matore non ha ancora raccolto il precedente;
» il consumatore pad leggere un dato alla volta e, naturalmente, solo dopo che sia stato prodotto dal
proceso produttore ¢ non deve leggere cue volte lo stesso valore:
» nel sistema non devono verificarsi situazioni di deadlock.
I due provessi deveno seambiarsi dei messadgi per comuntcarsi rispettivamente la dispontbilita del
dato da leggere e Vavvenuta lettura di dato e, quindi, la possibilita di produrre il successivo: ciascuno
processo deve attendere un segnale da parte dell'altro processo.
‘Una possibile soluzione in pseudo codifica che utilizza due semafori é la seguente:
semaphore piano = 0 // aii‘inizio 12 pumer non @ pienso (sem.r0s50)
semaphore vuoto = 1 i/ al1tinizio i1 bafer @ vuoto (sen. verde)
int sumer;
Processo preduci(int dato)
Pioucto) J) quando i1 buffer & muots
bufer = dato
V(pieno) // segnela che il bufer @ pieno
sn
Processo consuma()
P(pieno) // qaando ii buffer @ pieno
‘
v(vactey )/ sagnala che i1 buter & vuste
Il fanzionamento @ abbastanza semplice: vengono utilizzati due semafori il primo che indica quan-
doil buffer é pieno e quindi verra settato del produttore, il secondo che indica quando il dato @ stato
consumato, ¢ sara quindi settato dal consumatore: alla fine delle operaziont ogni processo deve
avvisare l'altro del cambiamento di stato e lo fara resettando il semaforo opportune:
» semaphore vuoto
= preduttore lo mette a 0 quando inizia a inserire un dato
= consumatore lo mette a 1 quando ha tolto un dato
» semaphore pieno
~ produtiore lo mette a 1 quando ha inserito un dato
~ consumatore lo mette a 0 quando inizia a togliere il datoUn produttore e un consumatore ¢ buffer eireolare
Nel caso pith generale la memoria condivisa pud contenere pit dati (o messaggi) e viene realizzata
per esempio con un buffer eireolare implementaco con un vettore di lunghezza DIM_BUFFER.
0 togi met DIM_BUFFER-
_— _—
ett togi
MModifichlamo le condiziont dl funzionamencto in:
¥ il produttore non pud inserire un messagsio nel buffer se questo & pieno:
9 il consumatore non pud prelevare un messaggio dal buffer se questo & vuoto
et DiMM_BUFFER.1
Introduciame due puntatori, mettie to» — gyEFER
alt, che individuano rispetcivamente la Set
prima porzione vuota e piena del buffer
dove inizialmente viene posto metti =
jae cota FIFO
con dmensione
I due puntatori metti e togli che si spo- DIM_BUFFER
stane attorng all'array realizzano una
coda cireolare FIFO con una dimensio-
ne massima prefissata (DIM_EUFFER):
yengono utilizzati in modo ciclice (mo:
dulo dell'ampiezza del buifer mediante
Yoperatore %)
Una coditica del proceso produttore & la seguente:
DIM_BUFFER
tog
Provesso produci(int dato)
P(wuoto) J/ qaanéo 41 bufer ha 2lmeno uno spazio woto
bafer[metti] = dato;
/| aggiornamento indica: quando
metei = (metti +1) & DIM_ BUFFER;
Y{pieno) // segnela che i1 buffer @ pieno
{ arrive OIMBUFFER #i ritorna a 0Una codifica del processo consumatore é la seguente:
Processo consuma()
inizio
P(pieno} W/ quando 1 bufer & pieno
togl: =(sogli+1)$D1M_ BUFFER;
u(weoto) J/ gagnala che i1 bafer & vuoto
// aiitinizio G2 bufler ha posti liberi
// dati da leggare presenti nel bufer
Pia produttori ¢ pit consumatori
Estendiamo ora al caso generale, dove possono essere presenti pitt produttori e pitt consumatori
questa nuova situazione pud comportare nuove necessita di sineronizzazione in quanto ora non &
sufficiente la sincronizzazione appena descritta, poiché abbiamo due tipi di problematiche:
sincronizzare | produttort e { consumatori, cos! come nel caso precedente, verificando che se qua
lungue dei produttori volesse produrre ma non ci fosse la disponibilita di un butfer libero, questo
deve sospendersi e attendere che uno qualungue dei consumatori consumi un dato liberando cosi
lo spazio di un buffer;
incronizzare gi n produttori fra di loro e gli m consumatori fra di loro perché in questa condi
zione anche le varicbili togli e metti sono condivise e quindi divengono di fatto risorse comuni.
Se per esempio due produttori contemporaneamente effettuano la P(V) ed entrano nella regione cri-
tica, molto probabilmente scriveranno nella stessa cella ed eseguiranno in modo errato l'operazione:
metti = (metti + 1) & DIM BUFFER;
Dobhiamo risolvere questo conflitto nei preduttor cost come lo ritroviamo simile anche net lettort,
che potrebbero leggere contemporaneamente lo stesso dato.
Inizialmente definiamo i semafori « il buffer comune:
senapnore yuoto = DIM.BUFFER // ail‘inizio 11 puter na posti 1iperieenaphore piene = 0 // aati da leggare preseati nel bufer
bufer array [01M BUFFER)
Per i procuittor! useremo quindi una procedura come quella di seiaite riportata, che dopo aver a
certata lesistenza di almeno un buffer libero, serializza tra i
produttori accesso alla variabile metti in modo da garantire
Ja mutua esclusione al suo accesso da parte dei produttori
che richiedono di aggiornarla.
Proceso produei(int dato)
sensphore mutex? = 1 // senaforo che regola i produttori
int tempo;
(uote) {/ quando it buffer ha spezio libero
(mutex?) // blocea l'accesso alla RC agli altri produttori
tempo = mete: // salva il valore corrente di metti
metti =(mettiel)€ DIN SUFFER; // aggiornamento dell‘incice
vy (maten>) // Libera iraccessc alla RC agli altri produetori
buferimetti} = dato // insarisci i1 move dato
V{pieno) // secnala che it buffer @ pieno
Per i consumatori useremo quindi une procedura simile, di seguito riportata:
Proceso consums( )
int tempo;
semaphore mutex = 1 // senaforo che regols 1 consunator!
(piano) J) quands 32 boffer & pier
(mutex?) // blocea 1'sccesso alla RC agli altri produttori
‘tempo stogli // salva il valore corrente di metti
togli =(togli+i)4D1M_SuPFER;
(mutex?) // Libera 1'accesso alla RC agli altri produttori
vivucte) // segnaia che it buffer @ vaoto
Bisogna stare attenti a non precludere il parallelismo del riempimento del buffer da parte dei pro-
dutori, soprattutto se le operazioni di serittura nel buffer sono piuittosto lente: se cos) fosse, tutti
4h altri produttori rimarrebbero in attesa per lungo tempo anche in presenza di posti liberi, e anche
tutti { consumatori rimarrebbero fermi, non avendo nulla da “consumare”,
Per questo motivo sono state portate al di fuori della regione critica le operazioni di deposito e di
prelievo dei dati.