Esperimenti con semafori e monitor in C
Ho messo a vostra disposizione alcuni sorgenti che implementano in C le astrazioni di semaforo e monitor come le abbiamo viste (o vedremo presto) a lezione.
Ho messo il materiale in questo file.
La directory comprende:
- un Makefile
- tlist.[ch] un modulo di implementazione di liste circolari di thread_id (usate per gestire i processi bloccati). Le liste possono essere usate come code o stack.
- suspend.[ch] è il modulo che implementa la sospensione e riattivazione dei pthread (usando il segnale SIGUSR1).
- semaphore.[ch] implementazione dei semafori fair
- monitor.[ch] implementazione dei monitor (con semantica signal urgent per le variabili di condizione).
- prod_cons.c esempio: produttore consumatore con i semafori
- bounded_buf.c esempio: bounded_buffer con i semafori
- bounded_buf_mon.c esempio: bounded buffer con i monitor
- philo.c: cena dei filosofi (la soluzione è volutamente errata e può generare deadlock)
--FedericoB (talk) 20:51, 14 November 2016 (CET)
Ho aggiunto in questo file.
- binsemaphore.[ch] semafori binari implementati senza semafori normali
- dirtybinsemaphore.c semafori binari implementati con semafori normali
- prod_consBin.c problema del produttore-consumatore risolto con solo un semaforo(binario). (warning contiene useless wait)
- dirtysemaphore.c semafori ordinari implementati con semafori binari
- readerWriters.c soluzione del problema lettori-scrittori che abbiamo discusso qui
Nel makefile ho aggiunto:
- il target prod_consBin e dirtyprod_consBin in cui cambia solo il tipo di semaforo binario linkato.
- il target readerWriters e dirtyreaders_writers in cui cambia solo il tipo di semaforo ordinario linkato.
Renzo (talk) 09:01, 15 November 2016 (CET)
attenzione: l'implementazione di FedericoB e' un ottimo punto di partenza per la discussione ma non e' corretta,
Infatti non rispetta l'invariante dei semafori binari 0 <= nV + Init - nP <= 1.
Poniamo infatti Init = 0, e sottoponiamo l'implementazione di Federico alla sequenza V V P V.
La prima V porta il semaforo al valore 1, la seconda blocca il chiamante, la P successiva trova il semaforo a 1 quindi non e' bloccante e quindi (codice alla mano) decrementa il semaforo (s->value--) e risveglia il processo bloccato per la seconda V. Il semaforo ora vale zero. L'ultima V non e' quindi bloccante.
quindi Init=0, nV=3, nP=1. (Nv + Init - Np) vale quindi 0 + 3 - 1 cioe' 2. Ahi! Dov'e' l'errore?
--FedericoB (talk) 10:54, 15 November 2016 (CET)
Ho sistemato creando prima i semafori binari implementati con i semafori normali.
Dopo sono riuscito a sistemare anche i semafori binari senza semafori normali. L'unico difetto è che usano 2 value invece che 1.
Renzo (talk) 12:03, 15 November 2016 (CET)
Ancora ottimi spunti di discussione, ma non ancora perfetti.... Prendiamo l'implementazione dei semafori binari con i semafori ordinari e il caso Init 1, V V P P.
Per effetto dell'Init sem0 vale 1 e sem 1 vale 0.
Il primo V porta sem0 a 2 e il chiamante si ferma su sem1. Il secondo V porta sem0 a 3 e ferma anch'esso il chiamante su sem1. La prima P supera la P su sem0 (che viene portato a 2) fa la V su sem1 (che ora vale 1) ed esce. La seconda P porta sem0 a 1, sem1 a 2 ed esce. Abbiamo lasciato I due processi fermi sulla P(sem1).
Prima o poi si sbloccheranno.... ma in questo momento nP (il numero di processi che hanno superato la P) vale 2, nV (il numero di processi che hanno superato la V) e' zero, Init era 1. quindi nV + Init - nP = 0 + 1 - 2 = -1. Ahime'! Come si risolve?
--FedericoB (talk) 10:52, 18 November 2016 (CET)
Ho sistemato i semafori binari e ho aggiunto i semafori ordinari implementati con i binari.
Ho tenuto il problema del produttore consumatore con il solo semaforo binario. Funziona ma abbiamo visto che può far aspettare inutilmente il consumatore.
S.G (talk) 10:36, 18 November 2016 (CET)
Ho dei dubbi riguardo la seguente parte "La prima P supera la P su sem0 (che viene portato a 2) fa la V su sem1 (che ora vale 1) ed esce". La prima P viene superata, mentre la V su sem1 (verifica che la coda sia vuota, se lo è incrementa di uno il semaforo, altrimenti risveglia un processo) ha in coda alcuni processi, e quindi non dovrebbe incrementare sem1.
uso dei semafori
Se volete fare altri esperimenti con i semafori dovete includere il file semaphore.h nei vostri sorgenti C.
Un semaforo ha tipo semaphore e viene creato in questo modo
semaphore mutex;
mutex = mutex_create(1);
(ovviamente le variabili semaphore devono essere condivise e 1 è solo l'esempio di valore iniziale per un semaforo mutex)
Le operazioni P e V su mutex si scrivono così:
semaphore_P(mutex);
semaphore_V(mutex);
produttore e consumatore
Il file prod_cons.c implementa una soluzione del problema del produttore/consumatore.
Il buffer e i semafori sono definiti come variabili condivise.
semaphore full;
semaphore empty;
volatile int buffer;
Il main inizializza i semafori e crea i thread.
int main(int argc, char *argv[]) {
pthread_t prod_t;
pthread_t cons_t;
full=semaphore_create(0);
empty=semaphore_create(1);
pthread_create(&prod_t, NULL, producer, NULL);
pthread_create(&cons_t, NULL, consumer, NULL);
pthread_join(prod_t, NULL);
pthread_join(cons_t, NULL);
}
La sincronizzazione fra produttore e il consumatore viene realizzata come segue:
void *producer(void *arg) {
while (1) {
int value;
// produce value
semaphore_P(empty);
buffer = value;
semaphore_V(full);
}
}
void *consumer(void *arg) {
while (1) {
int value;
semaphore_P(full);
value = buffer;
semaphore_V(empty);
// consume value
}
}
Uso dei monitor
Un monitor viene creato con la funzione:
monitor mymon;
mymon = monitor_create()
Le variabili di condizione vengono create nel seguente modo:
condition oktoproceed;
oktoproceed = condition_create(mymon);
(NB: le variabili di condizione sono sempre riferite ad uno specifico monitor).
Le procedure entry sono normali funzioni C ma occorre mettere all'entrata e all'uscita le chiamate monitor_{enter,exit}':
int mymon_dosomething(/* params */) {
rval int;
monitor_enter(mymon);
....
monitor_exit(mymon);
return rval;
}
Le operazioni wait e signal sulle variabili di condizione si scrivono così:
condition_wait(oktoproceed);
condition_signal(oktoproceed);
Bounded buffer con i monitor
Vengono definite come variabili condivise il monitor e le variabili di condizione
monitor bb;
condition ok2write;
condition ok2read;
oltre al buffer e alle variabili per la gestione delle strutture dati, fra le quali il contatore buflen.
Il monitor viene creato da questa funzione:
void bb_create(void) {
bb = monitor_create();
ok2write = condition_create(bb);
ok2read = condition_create(bb);
}
che viene richiamata dal main prima di far partire i thread:
int main(int argc, char *argv[]) {
pthread_t prod_t;
pthread_t cons_t;
bb_create();
pthread_create(&prod_t, NULL, producer, NULL);
pthread_create(&cons_t, NULL, consumer, NULL);
pthread_join(prod_t, NULL);
pthread_join(cons_t, NULL);
}
Il monitor implementa due procedure entry put e get:
void bb_put(int value) {
monitor_enter(bb);
if (buflen >= SIZE)
condition_wait(ok2write);
// enqueue value in the buffer
buflen++;
condition_signal(ok2read);
monitor_exit(bb);
}
int bb_get(void) {
int rval;
monitor_enter(bb);
if (buflen <= 0)
condition_wait(ok2read);
// dequeue rval from the buffer
buflen--;
condition_signal(ok2write);
monitor_exit(bb);
return rval;
}
Il codice dei processi produttore e del consumatore usa le procedure entry del monitor bb in questo modo:
void *producer(void *arg) {
while (1) {
int value;
// produce value
bb_put(value);
}
}
void *consumer(void *arg) {
while (1) {
int value;
value = bb_get();
// consume value
}
}