Sommario
Due categorie di processi:
Vincoli
I vincoli imposti da un problema di tipo Produttore-Consumatore,nel caso che il buffer sia unico e che vi siano un solo produttore ed un solo consumatore, sono i seguenti:
Per la sincronizzazione dei processi produttore e consumatore si utilizzano due semafori:
Il messaggio prodotto consiste di un valore di tipo long, rappresentativo della data di sistema (ottenuto mediante la chiamata gettimeofday(2)) .
Una siffatta soluzione permette di differenziare i valori prodotti dai vari processi produttori.
La produzione ed il consumo avvengono rispettivamente all’interno delle procedure
void Produttore(msg*,int);
void Consumatore(msg *,int);
all’interno delle quali si effettuano anche le operazioni di Wait_Sem e Signal_Sem necessarie.
//Procedure Consumatore
void Consumatore(msg *ptr_sh,int mutex){
msg mess;
Wait_Sem(mutex,MSG_DISP);
mess=*ptr_sh;
printf("Messaggio letto: \n",mess);
Signal_Sem(mutex,SPAZIO_DISP);
}
//Procedure Produttore
void Produttore(msg*ptr_sh,int mutex){
// Produzione del valore
struct timeval t1;
struct timezone t2;
gettimeofday(&t1,&t2);
msg val =t1.tv_usec;
Wait_Sem(mutex,SPAZIO_DISP);
printf ("Produzione in corso...");
*ptr_sh=val;
Signal_Sem(mutex,MSG_DISP);
}
La coda è implementata mediante i seguenti campi:
Anche le variabili testa e coda devono essere condivise!!
Per la sincronizzazione dei processi sono stati utilizzati due semafori, SPAZIO_DISP e NUM_MESS, che indicano rispettivamente la presenza di spazio disponibile in coda per la produzione di un messaggio e il numero di messaggi presenti in coda.
SPAZIO_DISP ha valore iniziale DIM (dimensione della coda).
NUM_MESS ha valore iniziale 0.
void Produttore(int* testa, msg* buffer, int sem) {
Wait_Sem(sem, SPAZIO_DISP);
// Produzione di valore_prodotto . . .
buffer[(testa)]=valore_prodotto;
testa=++(testa) % DIM; //gestione circolare
Signal_Sem(sem, NUM_MESS); //nelem=nelem+1
}
void Consumatore(int* coda, msg* buffer, int sem){
msg mess;
Wait_Sem(sem, NUM_MESS);
// Consumo
mess=buffer[coda];
(coda)=++(coda) %DIM; // gestione circolare
printf("Messaggio letto: <%d>\n",mess);
Signal_Sem(sem, SPAZIO_DISP); // nelem=nelem-1
}
Con questa soluzione il produttore e il consumatore non accedono mai contemporaneamente alla stessa posizione della coda dei messaggi
… tuttavia va bene solo nel caso in cui si ha 1 processo produttore e 1 processo consumatore!
Nell’ipotesi in cui vi siano più produttori e più consumatori che accedono allo stesso buffer, le operazioni di deposito e prelievo devono essere eseguite rispettivamente in mutua esclusione, ed essere quindi programmate come sezioni critiche.
A tal fine, bisogna introdurre due nuovi semafori:
void Produttore(int* testa, msg* buffer, int sem) {
Wait_Sem(sem, SPAZIO_DISP);
Wait_Sem(sem, MUTEX_W);
// Produzione di valore_prodotto . . .
buffer[(testa)]=valore_prodotto;
testa=++(testa) % DIM; // gestione circolare
Signal_Sem(sem, MUTEX_W);
Signal_Sem(sem, NUM_MESS); // nelem=nelem+1
}
void Consumatore(int* coda, msg* buffer, int sem){
msg mess;
Wait_Sem(sem, NUM_MESS); Wait_Sem(sem, MUTEX_R);
// Consumo
mess=buffer[coda];
(coda)=++(coda) %DIM; // gestione circolare
printf("Messaggio letto: <%d> \n",mess);
Signal_Sem(sem, MUTEX_R);
Signal_Sem(sem, SPAZIO_DISP); // nelem=nelem-1
}
La soluzione precedente penalizza produttori o consumatori “veloci” in presenza di produttori o consumatori “lenti” (evento che può accadere, ad esempio, quando i messaggi prodotti hanno dimensione diversa).
Soluzione: utilizzo di un pool di buffer gestito mediante un vettore ausiliario di STATO
Una volta che un processo acquisisce il suo buffer, può procedere in concorrenza agli altri.
I vincoli imposti sono i seguenti:
La gestione del pool di buffer avviene mediante due vettori:
Per la gestione della mutua esclusione nell’accesso al buffer di stato, si possono utilizzare due mutex, MUTEXP e MUTEXC, uno per i produttori e uno per i consumatori, entrambi inizializzati a 1.
I soliti due semafori, SPAZIO_DISP e MSG_DISP, usati per la sincronizzazione delle operazioni di produzione e consumo, con SPAZIO_DISP inizializzato a DIM (o al numero di produttori, se minore o uguale a DIM), e MESS_DISP inizializzato a 0.
void Produttore(int* stato, msg* buffer, int sem) {
int indice = 0;
Wait_Sem(sem, SPAZIO_DISP); // attende spazio
Wait_Sem(sem, MUTEXP); // Blocca altri prod
// determina l'indice del primo elemento VUOTO
while ((indice <=DIM)&&(stato[indice]!=VUOTO))
indice++;
stato[indice]=IN_USO; // la cella è in uso
Signal_Sem(sem, MUTEXP); // Sblocca altri prod
// Produzione di valore_prodotto . . .
buffer[indice]=valore_prodotto;
stato[indice]=PIENO; // la cella è piena
Signal_Sem(sem, MSG_DISP); // segnala i cons
}
void Consumatore(int* stato, msg* buffer, int sem) {
int indice = 0;
Wait_Sem(sem, MSG_DISP); // attende messaggio
Wait_Sem(sem, MUTEXC); // Blocca altri cons
// determina l'indice del primo elemento PIENO
while ((indice <=DIM)&&(stato[indice]!=PIENO))
indice++;
stato[indice]=IN_USO; // la cella è in uso
Signal_Sem(sem, MUTEXC); // Sblocca altri cons
// consumo del messaggio
msg valore_consumato = buffer[indice];
stato[indice]=VUOTO; // la cella è vuota
Signal_Sem(sem, SPAZIO_DISP); // segnala i prod
}
Con questa soluzione, i produttori (o i consumatori) accedono alla sezione critica gestita da MUTEXP (o MUTEXC) solo per acquisire una cella libera tramite il vettore di stato.
Una volta acquisita la cella, possono procedere concorrentemente nella produzione (o consumo).
I produttori “veloci” potranno terminare prima e segnalare prima i consumatori (e viceversa).
Due categorie di processi:
Vincoli:
Le operazioni di lettura sono “protette” dalle procedure di Inizio_Lettura() e Fine_Lettura(),
Le operazioni di scrittura sono “protette” da Inizio_Scrittura() e Fine_Scrittura().
Un processo lettore attende solo se la risorsa è occupata da un processo scrittore e un processo scrittore può accedere alla risorsa solo se questa è libera.
Questa particolare strategia di sincronizzazione può tuttavia provocare condizioni di attesa indefinita (STARVATION) per i processi scrittori.
Una variable condivisa NUM_LETTORI viene usata per contare il numero di lettori che contemporaneamente accedono alla risorsa.
Solo quando NUM_LETTORI = 0, gli scrittori possono accedere (uno alla volta) alla risorsa.
Si utilizzano 2 semafori:
void Inizio_Lettura(int sem) {
Wait_Sem(sem, MUTEXL);
Num_Lettori++;
if (Num_Lettori==1) Wait_Sem (sem, SYNCH);
Signal_Sem(sem, MUTEXL);
}
void Fine_Lettura(int sem) {
Wait_Sem(sem, MUTEXL);
Num_Lettori--;
if (Num_Lettori==0) Signal_Sem (sem, SYNCH);
Signal_Sem(sem, MUTEXL);
}
void Inizio_Scrittura(int sem) {
Wait_Sem(sem, SYNCH);
}
void Fine_Scrittura(int sem) {
Signal_Sem(sem, SYNCH);
}
Starvation degli scrittori: mentre uno scrittore è sospeso sul semaforo SYNCH, altri lettori possono accedere alla risorsa, ritardando indefinitamente lo scrittore.
Anche per gli scrittori si può ottenere un comportamento analogo, introducendo una variabile NUM_SCRITTORI.
In questo caso occorrono 4 semafori, tutti inizializzati a 1:
void Inizio_Scrittura(int sem) {
Wait_Sem(sem, MUTEXS);
Num_Scrittori++;
if (Num_Scrittori==1) Wait_Sem(sem, SYNCH);
Signal_Sem(sem, MUTEXS);
Wait_Sem(sem, MUTEX)
}
void Fine_Scrittura(int sem) {
Signal_Sem(sem, MUTEX)
Wait_Sem(sem, MUTEXS);
Num_Scrittori--;
if (Num_Scrittori==0) Signal_Sem (sem, SYNCH);
Signal_Sem(sem, MUTEXS);
}
1. Introduzione ai Sistemi Operativi
5. Scheduling nei sistemi mono-processore
6. Threads, SMP
8. Scheduling Multiprocessore e Real-Time
9. Gestione dei processi nei sistemi operativi Unix/Linux e Window...
10. Introduzione alla Programmazione Concorrente
11. Sincronizzazione nel modello ad ambiente globale
12. Problemi di cooperazione nel modello ad ambiente globale
14. Sincronizzazione nel modello ad ambiente locale
15. Deadlock
16. Programmazione Multithread
18. Memoria Virtuale
20. Il File System
21. Primitive di sincronizzazione nel kernel Linux
22. Esercitazione: System call per la gestione dei processi
23. Esercitazione: Inteprocess Communication e Shared Memory
24. Esercitazione: System Call per la gestione dei semafori in Linu...
25. Esercitazione: Problema dei Produttori e dei Consumatori
26. Posix Threads
Ancillotti – Boari, Principi e Tecniche di Programmazione concorrente, par. 4.2.2