Semafori POSIX

I semafori POSIX sono semafori contatori che permettono di gestire la sincronizzazione dei thread POSIX. Esistono altri meccanismi che, per mancanza di tempo, menzionano solamente: I Pthread Mutex sono semafori binari mentre le Pthread Condition vengono utilizzate per ‘simulare’ il costrutto dei monitor che vedremo invece nel linguaggio Java.

I semafori POSIX si utilizzano tramite le seguenti strutture dati e funzioni, definite nella libreria semaphore.h:

  • sem_t sem_name: dichiara una variabile di tipo semaforo;
  • int sem_init(sem_t *sem, int pshared, unsigned int value) inizializza il semaforo sem al valore value. la variabile pshared indica se il semaforo è condiviso tra thread (uguale a 0) o processi (diverso da 0), lo useremo quindi sempre con 0.
  • int sem_wait(sem_t *sem) esegue una P(sem);
  • int sem_post(sem_t *sem) esegue una V(sem);
  • int sem_getvalue(sem_t *sem, int *val) legge il valore del semaforo e lo copia in val;
    ATTENZIONE: in alcune implementazioni il semaforo rosso è 0, in altre è negativo (e indica il numero di processi in attesa);
  • sem_destroy(sem_t *sem) elimina il semaforo. Da NON usare se ci sono processi in attesa sul semaforo (comportamento non specificato).


NOTA PER GLI UTENTI MAC: macOS non supporta i semafori ‘unnamed’ descritti qui sopra. Per poter utilizzare i semafori POSIX si devono usare i semafori con nome (che tra l’altro sono facilmente condivisibili tra processi, un po’ come le pipe con nome). Al posto della sem_init si deve usare la sem_open e al posto della sem_destroy si usano sem_close e sem_unlink. Ovviamente i semafori con nome si possono utilizzare anche in Linux.

Esercizio 1: Sezione Critica

Riprendiamo l’ultimo esercizio della volta scorsa:

Creare 2 thread che aggiornano ripetutamente (in un ciclo for) una variabile condivisa count per un numero elevato di volte (ad esempio 1000000). Stampare il valore finale per osservare eventuali incrementi perduti…

(Seguiva una nota sulle ottimizzazioni del compilatore. Vedere le dispense della volta scorsa per maggiori dettagli)

Aggiungere un semaforo mutex per risolvere le interferenze. È sufficiente aggiungere una variabile globale sem_t sem e, all’inizio e alla fine del main, l’inizializzazione e la rimozione del semaforo: sem_init(&sem,0,1) e sem_destroy(&sem). Infine, per proteggere la sezione critica, aggiungere sem_wait(&sem) e sem_post(&sem) prima e dopo la lettura/modifica di count. Notare l’esecuzione corretta al prezzo di una più bassa performance.

NOTA: per OS X si useranno sem=sem_open("mymutex",O_CREAT,0700,1) e sem_close(sem); sem_unlink("mymutex"); facendo attenzione che in questo caso sem sarà un puntatore a sem_t (quindi anche la wait e la post andranno fatte su sem e non su &sem).

Esercizio 2: attesa tra 2 thread

Realizzare la sincronizzazione tra 2 thread vista a lezione in cui T2, prima di eseguire la porzione di codice “D” deve attendere che T1 abbia eseguito il codice “A”.

ESERCIZIO 3: Produttore-Consumatore

Implementare il Produttore-Consumatore con buffer circolare visto a lezione, utilizzando due semafori contatori più un mutex. Testarlo in presenza di più produttori e più consumatori, verificando che la presenza del mutex è fondamentale per la coerenza dei dati.

Inventarsi a tale scopo un test di qualche genere. Ad esempio si può tenere nelle celle vuote un valore speciale (-1) e testare se si sta leggendo una cella vuota o scrivendo in una piena. Ovviamente il consumatore deve scrivere nella cella il valore speciale, dopo che ha letto. Il test dovrebbe segnalare problemi di sincronizzazione nel caso non si utilizzino appropriatamente i semafori.

NOTA: l’output a video talvolta riduce le interferenze in quanto crea una alternanza molto forte nell’esecuzione dei thread. In tale caso, provare a fare la ridirezione (tramite il simbolo >) su un file.

BONUS: Le tre migliori soluzioni con test, e esaurientemente commentate, che verrano postate sul sito, avranno un bonus aggiuntivo sul voto finale.
(istruzioni per pubblicare il codice).

3 thoughts on “Semafori POSIX”

  1. Prova pubblicazione codice (ricordatevi di rimpiazzare le virgolette in “C” se fate taglia e incolla da qui)

    1. /* IMPLEMENTAZIONE PRODUTTORE-CONSUMATORE CON SEMAFORI POSIX
       * Carlo Passadore 862875
       * Il seguente programma realizza una soluzione al problema produttore-consumatore utilizzando i semafori posix,
       * vengono utilizzati dei thread produttori che creano dei numeri casuali da 0 a 100 
       * e vengono salvati in una coda circolare che viene così occupata,
       * i thread consumatori invece leggono dalla coda e stampano nello standard output i numeri
       * liberando la cella della coda da cui viene letto il numero.
       * Agni cella corrisponde una variabile booleana 'vuoto' che è vera quando la cella corrispondente è vuota,
       * questa variabile viene usata per verificare la presenza di errori.
       * Quando viene rilevato un errore viene stampato un messaggio di errore nello standard output.
       * Per sezione critica si intendono le operazioni di lettura e scrittura nella coda.
       * Verranno realizzate due sincronizzazioni: per evitare di sovrascrivere il produttore deve attendere quando la coda
       * è piena, invece quando il buffer è vuoto il consumatore deve attendere per evitare di leggere vuote.
       * La soluzione è usare due semafori distinti che regolano l’accesso a risorse: 'semInserimento' inizializzato a DIM_CODA
       * che sarebbe il numero di celle inizialmente vuote che regola la scrittura e ‘semLettura’ inizializzato a 0 cioè il numero di celle vuote
       * che regola la lettura.
       */
      
      #include <stdio.h> 
      #include <pthread.h> 
      #include <semaphore.h> 
      #include <unistd.h>
      #include <stdlib.h>
      
      #define DIM_CODA 5 //dimensione della coda
      #define N_PRODUTTORI 6 //numero di thread produttori
      #define N_CONSUMATORI 3 //numero di thread consumatori
      
      typedef struct cella { //tipo delle celle che formerà la coda
          char vuoto; //vero se la cella è vuota, falso altrmenti
      	int num; //variabile per salvare i numeri generati
      }cella;
      
      typedef struct coda { //ti coda in cui i thread accederanno in lettura e scrittura
      	cella buffer[DIM_CODA]; //buffer circolare di dimensione DIM_CODA in cui saranno salvati i numeri random generati
      	int inserisci; //indice per l'inserimento
      	int preleva; //indice per il prelievo 
      }coda;
      
      sem_t mutex; //semaforo per gestire la sezione critica
      sem_t semInserimento; //semaforo per la concorrenza nell'inserimento
      sem_t semLettura; //semaforo per la concorrenza nella lettura
      coda randomCoda; //coda in cui i thread accederanno in lettura e scrittura
      
      //restituisce l'indice per la lettura corrente e aggiorna la variabile 'c->preleva' per l'operazione seguente
      int prelevaNext(coda *c) {
      	int i = c->preleva;
      	c->preleva = (c->preleva+1 < DIM_CODA) ? c->preleva+1 : 0;
      	return i;
      }
      
      //restituisce l'indice per la scrittura corrente e aggiorna la variabile 'c->inserisci' per l'operazione seguente
      int inserisciNext(coda *c) {
      	int i = c->inserisci;
      	c->inserisci = ((c->inserisci)+1 < DIM_CODA) ? (c->inserisci)+1 : 0;
      	return i;
      }
      
      //genera un numero random e lo memorizza nella coda
      void * produci(void * a) {
      	int i = 0;
      	int r = 0;
      	
      	while(1){
      		sleep(1); //aspetta 1 secondo
      		r = rand() % 101; //genera un numero random da 0 a 100
      		
      		sem_wait(&semInserimento); //quando il buffer è pieno il produttore deve attendere per evitare di sovrascrivere
      		sem_wait(&mutex); //attendi l'accesso alla sezione critica
      		
      		i = inserisciNext(&randomCoda); //troviamo l'indice per la scrittura dalla coda
      		if( ! (randomCoda.buffer)[i].vuoto){ //se per errore stiamo scrivendo in una cella non vuota segnaliamo l'errore
      			printf("ERRORE: scrittura su cella non vuota\n");
      		}
      		((randomCoda.buffer)[i]).num = r; //salviamo il numero nella coda
      		((randomCoda.buffer)[i]).vuoto = 0; //segnaliamo che la cella è occupata
      
      		sem_post(&mutex); //fine della sezione critica
      		sem_post(&semLettura); //fine operazioni di scrittura
      	}
      
          pthread_exit(NULL);
      }
      
      //legge dalla coda un numero random e lo stampa
      void * consuma(void * a) {
      	int i = 0;
      	int r = 0;
      	
      	while(1){
      		sem_wait(&semLettura); //quando il buffer è vuoto il consumatore deve attendere per evitare di leggere celle non ancora scritte
      		sem_wait(&mutex); //attendi l'accesso alla sezione critica
      		
      		i = prelevaNext(&randomCoda); //troviamo l'indice per la lettura dalla coda
      		if((randomCoda.buffer)[i].vuoto){ //se per errore stiamo leggendo una cella non vuota segnaliamo l'errore
      			printf("ERRORE: lettura da cella vuota\n");
      		}
      		r = ((randomCoda.buffer)[i]).num; //leggiamo il numero dalla coda
      		((randomCoda.buffer)[i]).vuoto = 1; //segnaliamo che la cella letta è tornata vuota
      		
      		sem_post(&mutex); //fine della sezione critica
      		sem_post(&semInserimento); //fine operazioni di lettura
      		
      		printf("%d\n",r);
      	}
          
          pthread_exit(NULL);
      }
      
      // Inizializza la coda e semafori
      void inizializza(coda *c) {
      	int i=0;
      	for(i=0;i<DIM_CODA;i++){
      		((c->buffer)[i]).num=0;
      		((c->buffer)[i]).vuoto=1; //all'inizio tutte le celle sono vuote
      	}
      	c->inserisci=0; //iniziamo a inserire dalla cella 0
      	c->preleva=0; //iniziamo legge dalla cella 0
      	sem_init( &mutex, 0, 1);
      	sem_init( &semInserimento, 0, DIM_CODA); //ci sono DIM_CODA celle vuote
      	sem_init( &semLettura, 0, 0); //ci sono 0 celle occupate
      }
      
      
      
      int main() {
      	int i=0;
          pthread_t produttori[N_PRODUTTORI];
          pthread_t consumatori[N_CONSUMATORI];
          
          inizializza(&randomCoda); //inizializziamo la coda
          
          srand(48); //settiamo il seme del generatore di numeri
          
          for (i=0;i<N_PRODUTTORI;i++) { //creiamo i thread produttori
      		pthread_create(&produttori[i],NULL,produci,NULL);
      	}
           
          for (i=0;i<N_CONSUMATORI;i++) { //creiamo i thread consumatori
      		pthread_create(&consumatori[i],NULL,consuma,NULL);
      	}
      	
      	for(i=0;i<N_PRODUTTORI;i++) { //il thread principale attende i thread produttori
      		pthread_join(produttori[i],NULL);
      	}
      	
      	for(i=0;i<N_CONSUMATORI;i++) { //il thread principale attende i thread consumatori
      		pthread_join(consumatori[i],NULL);
      	}
      	
      	return EXIT_SUCCESS;
      }
      
      
  2. #include <pthread.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <sys/syscall.h>
    #include <semaphore.h>
    
    #define MAX 8		//dimensione del buffer
    
    #define VALORI 5000	//totale valori generati/letti
    #define PRODUTTORI 10
    #define CONSUMATORI 10
    
    int buffer[MAX];
    int inserisci, preleva;
    
    sem_t mutex, piene, vuote;
    
    /*Programma di esempio in cui creiamo thread produttori e consumatori che leggono e scrivono
     * in maniera concorrente senza interferire tra di loro tramite l'utilizzo dei semafori.
     * mutex -> semaforo per garantire la mutua esclusione tra i thread dello stesso tipo nella
     * sezione critica in cui aggiorniamo inserisci/preleva
     * piene -> semaforo per bloccare eventuali consumatori in eccedenza
     * vuote -> semaforo per bloccare eventuali produttori in eccedenza
     * */
    
    //funzione per gestire eventuali errori durante la creazione o la join 
    int die(char * s, int e) {
    	printf("%s [%i]\n",s,e);
    	exit(1);
    }
    
    //funzione eseguita dai thread produttori
    void * produttore(void *num) {
    	for(int i = 0; i < VALORI/PRODUTTORI;i++){
    		sem_wait(&vuote);		//P(vuote)
    		sem_wait(&mutex);		//P(mutex)
    		//sez critica
    		if(buffer[inserisci] != -1){
    			printf("Errore durante la scrittura(thread %li)\n", syscall(SYS_gettid));
    			exit(EXIT_FAILURE);
    		}
    		else{
    			//DEBUG:printf("Sto scrivendo nella posizione %d\n", inserisci);
    			buffer[inserisci] = 1;
    			inserisci = (inserisci+1)%MAX;
    			//DEBUG:printf("ho scritto 1\n");
    		}
    		//fine sez critica
    		sem_post(&mutex);		//V(mutex)
    		sem_post(&piene);		//V(piene)
    	}
    	pthread_exit(NULL);
    }
    
    //funzione eseguita dai thread consumatori
    void * consumatore(void *a){
    	int num;
    	
    	for(int i = 0;i < VALORI/CONSUMATORI; i++){
    		sem_wait(&piene);		//P(piene)
    		sem_wait(&mutex);		//P(mutex)
    		//sez critica
    		if(buffer[preleva] == -1){	//sta per leggere da una posizione "sporca"
    			printf("Errore durante la lettura(thread %li)\n", syscall(SYS_gettid));
    			exit(EXIT_FAILURE);
    		}
    		else{
    			//DEBUG:printf("Sto prelevando dalla posizione %d\n", preleva);
    			num = buffer[preleva];
    			buffer[preleva] = -1;
    			//DEBUG:printf("letto dalla posizione %d\n",preleva);
    			preleva = (preleva+1)%MAX;
    			
    		}
    		//fine sez critica
    		sem_post(&mutex);		//V(mutex)
    		sem_post(&vuote);		//V(vuote)
    	}
    	pthread_exit(NULL);
    }
    	
    int main() {
    	pthread_t tid[PRODUTTORI+CONSUMATORI];
    	int i,err;
    	
    	//inizializzazione dei semafori
    	sem_init(&mutex, 0, 1);
    	sem_init(&piene, 0 ,0);
    	sem_init(&vuote, 0 , MAX);
    	
    	for(i = 0; i< MAX;i++){
    		buffer[i] = -1;
    	}
    	
    	inserisci = 0;
    	preleva = 0;
    	
    	//creazione dei produttori
    	for(i = 0; i < PRODUTTORI; i++){
            if (err=pthread_create(&tid[i],NULL,produttore, NULL))
                die("errore create",err);
        }
        
        //creazione dei consumatori
    	for(i; i < PRODUTTORI+CONSUMATORI; i++){
    		if(err=pthread_create(&tid[i],NULL,consumatore, NULL))
    			die("errore create",err);
    	}
    	
        // attende i thread
        for (i=0;i<PRODUTTORI+CONSUMATORI;i++)
            if (err=pthread_join(tid[i],NULL))
                die("errore join",err);
         
        printf("I thread hanno terminato l'esecuzione correttamente\n");
    }	
    

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.