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).

11 thoughts on “Semafori POSIX”

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

    1. Non capisco perché me le cancelli, comunque sono queste:
      pthread.h
      stdio.h
      stdlib.h
      unistd.h
      sys/syscall.h
      semaphore.h
      string.h

    1. @Ciampi: hai usato la variable i globale per gestire il ciclo dentro i thread. Non funziona (i thread non escono dal ciclo), inoltre se incrementi una variabile globale devi sempre proteggerla con una sezione critica.

  2. Primo esercizio:

    Secondo esercizio:

    Terzo esercizio:

  3.   /* ESERCIZIO 1 */
      #include <pthread.h>
      #include <stdio.h>
      #include <stdlib.h>
      #include <unistd.h>
      #include <sys/syscall.h>
      #include <errno.h>
      #include <string.h>
      #include <xlocale.h>
      #include <semaphore.h>
    
      #define MAX 1000000
    
      int count;
      sem_t* mutex;
    
      /*
      * funzione per gestire gli errori
      */
      void die( char* message, int error ){
        if( error == EINVAL ){
          printf("%s : codice errore EINVAL(%d): invalid argument\n",message, error);
        }
        else{
          printf("%s : codice errore sconosciuto (%d)\n",message, error);
        }
        exit(1);
      }
    
      /*
      * codice thread :
      * con il semaforo mutex gestisco l'accesso alla sezione critica
      * solamente un thread alla volta possono accederci
      */
      void* codiceThread( void* param ){
        int i = 0;
        for( i = 0; i < MAX; i++ ){
          sem_wait( mutex );
          count++;
          sem_post( mutex );
        }
        pthread_exit( NULL );
      }
    
      /*
      * creo un semaforo mutex inizializzato a 1
      * successivamente creo due thread che eseguono la funzione sopra
      * aspetto che tutti i thread concludano per chiudere e rimuovere il semaforo
      */
      int main( int argc, char* argv[] ){
        pthread_t threadID[2];
        int i = 0;
        int error;
        count = 0;
        mutex = sem_open( "mutex", O_CREAT, 0700, 1 );
        for( i = 0; i < 2; i++ ){
          error = pthread_create( threadID+i, NULL, codiceThread, NULL );
          if( error != 0 ){
            die( "errore creazione thread", error );
          }
        }
        for( i = 0; i < 2; i++ ){
          error = pthread_join( *(threadID+i), NULL );
          if( error != 0 ){
            die( "errore join", error );
          }
        }
        printf("I thread hanno concluso\nCount (%d) vale : %d \n", MAX*2, count);
        sem_close( mutex );
        sem_unlink( "mutex" );
        return 0;
      }
    
      /* ESERCIZIO 2 */
      #include <pthread.h>
      #include <stdio.h>
      #include <stdlib.h>
      #include <unistd.h>
      #include <sys/syscall.h>
      #include <errno.h>
      #include <string.h>
      #include <xlocale.h>
      #include <semaphore.h>
    
      int count;
      sem_t* mutex;
    
      /*
      * funzione per gestire gli erorri
      */
      void die( char* message, int error ){
        if( error == EINVAL ){
          printf("%s : codice errore EINVAL(%d): invalid argument\n",message, error);
        }
        else{
          printf("%s : codice errore sconosciuto (%d)\n",message, error);
        }
        exit(1);
      }
    
      /*
      * funzione per il primo thread
      * con il semaforo mutex inizializzato a 1
      * dopo che il codice A è stato eseguioto posso fare una post sull mutex
      */
      void* codiceThread1( void* param ){
        sleep(3);
        printf("Eseguito codice A\n");
        sem_post( mutex );
        sleep(3);
        printf("Eseguito codice B\n");
        pthread_exit( NULL );
      }
    
      /*
      * funzione per il secondo thread
      * avendo il mutex inialmente a 0 il codice D è costretto ad attendere la post del codice A
      * successivamente all'esecuzione del codice D faccio una post per segnalre verde il semaforo nuovamente
      */
      void* codiceThread2( void* param ){
        printf("Eseguito codice C\n");
        //ultima porzione di codice da eseguire prima A e B
        sem_wait( mutex );
        printf("Eseguito codice D\n");
        sem_post( mutex );
        pthread_exit( NULL );
      }
    
      int main( int argc, char* argv[] ){
        pthread_t threadID[2];
        int i = 0;
        int error;
        count = 0;
        mutex = sem_open ("mutex", O_CREAT, 0700, 0);
        error = pthread_create( threadID+0, NULL, codiceThread1, NULL );
        if( error != 0 ){
          die( "errore creazione thread", error );
        }
        error = pthread_create( threadID+1, NULL, codiceThread2, NULL );
        if( error != 0 ){
          die( "errore creazione thread", error );
        }
        for( i = 0; i < 2; i++ ){
          error = pthread_join( *(threadID+i), NULL );
          if( error != 0 ){
            die( "errore join", error );
          }
        }
        printf("I thread hanno concluso\n");
        sem_close( mutex );
        sem_unlink( "mutex" );
        return 0;
      }
    
      /* ESERCIZIO 3 */
      #include <pthread.h>
      #include <stdio.h>
      #include <stdlib.h>
      #include <unistd.h>
      #include <sys/syscall.h>
      #include <errno.h>
      #include <string.h>
      #include <xlocale.h>
      #include <semaphore.h>
    
      #define BUFFER_SIZE 10
      #define MAX 100
    
      sem_t* mutex;
      sem_t* piene;
      sem_t* vuote;
      char buffer[BUFFER_SIZE];
      int indexP;
      int indexC;
    
      /*
      * funzione per mostrare le istruzioni di invocazione del programma in caso di errori
      */
      void die (){
        printf("\nIstruzioni:\n\nSe non si vuole specificare il numero di produttori e consumatori sarà impostato di default a 1 e 1\nInvocare il programma come segue:\n./nomeProgramma\n\nAltrimenti specificarne il numero (deve essere pari)\nInvocare il programma come segue:\n./nomeProgramma -s valore\n(per esempio: ./semaphore -s 4)\n");
        exit(1);
      }
    
      /*
      * funzione per gestire gli errori di errno.h
      */
      void errorHandler (char* message, int error) {
        switch (error) {
          case EPERM:
          printf("%s : EPERM(%d) Operation not permitted\n", message, error);
          break;
          case ENOENT:
          printf("%s : ENOENT(%d) No such file or directory\n", message, error);
          break;
          case ESRCH:
          printf("%s : ESRCH(%d) No such process\n", message, error);
          break;
          case EINTR:
          printf("%s : EINTR(%d) Interrupted system call\n", message, error);
          break;
          case EIO:
          printf("%s : EIO(%d) Input/output error\n", message, error);
          break;
          case ENXIO:
          printf("%s : ENXIO(%d) Device not configured\n", message, error);
          break;
          case E2BIG:
          printf("%s : E2BIG(%d) Argument list too long\n", message, error);
          break;
          case ENOEXEC:
          printf("%s : ENOEXEC(%d) Exec format error\n", message, error);
          break;
          case EBADF:
          printf("%s : EBADF(%d) Bad file number\n", message, error);
          break;
          case ECHILD:
          printf("%s : ECHILD(%d) No spawned processes\n", message, error);
          break;
          case EAGAIN:
          printf("%s : EAGAIN(%d) Resource temporarily unavailable\n", message, error);
          break;
          case ENOMEM:
          printf("%s : ENOMEM(%d) Cannot allocate memory\n", message, error);
          break;
          case EACCES:
          printf("%s : EACCES(%d) Access denied\n", message, error);
          break;
          case EFAULT:
          printf("%s : EFAULT(%d) Bad address\n", message, error);
          break;
          case ENOTBLK:
          printf("%s : ENOTBLK(%d) Not block device\n", message, error);
          break;
          case EBUSY:
          printf("%s : EBUSY(%d) Device busy\n", message, error);
          break;
          case EEXIST:
          printf("%s : EEXIST(%d) File exist\n", message, error);
          break;
          case EXDEV:
          printf("%s : EXDEV(%d) Cross-device link\n", message, error);
          break;
          case ENODEV:
          printf("%s : ENODEV(%d) Operation not supported by device\n", message, error);
          break;
          case ENOTDIR:
          printf("%s : ENOTDIR(%d) Not a directory\n", message, error);
          break;
          case EISDIR:
          printf("%s : EISDIR(%d) Is a directory\n", message, error);
          break;
          case EINVAL:
          printf("%s : EINVAL(%d) Invalid argument\n", message, error);
          break;
          case ENFILE:
          printf("%s : ENFILE(%d) Too many open files in system\n", message, error);
          break;
          case EMFILE:
          printf("%s : EMFILE(%d) Too many files open\n", message, error);
          break;
          case ENOTTY:
          printf("%s : ENOTTY(%d) Inappropriate ioctl for device\n", message, error);
          break;
          case ETXTBSY:
          printf("%s : ETXTBSY(%d) Unknown error\n", message, error);
          break;
          case EFBIG:
          printf("%s : EFBIG(%d) File too large\n", message, error);
          break;
          case ENOSPC:
          printf("%s : ENOSPC(%d) No space left on device\n", message, error);
          break;
          case ESPIPE:
          printf("%s : ESPIPE(%d) Illegal seek\n", message, error);
          break;
          case EROFS:
          printf("%s : EROFS(%d) Read-only file system\n", message, error);
          break;
          case EMLINK:
          printf("%s : EMLINK(%d) Too many links\n", message, error);
          break;
          case EPIPE:
          printf("%s : EPIPE(%d) Broken pipe\n", message, error);
          break;
          case EDOM:
          printf("%s : EDOM(%d) Numerical arg out of domain\n", message, error);
          break;
          case ERANGE:
          printf("%s : ERANGE(%d) Result too large\n", message, error);
          break;
          case EDEADLK:
          printf("%s : EDEADLK(%d) Resource deadlock avoided\n", message, error);
          break;
          case ENAMETOOLONG:
          printf("%s : ENAMETOOLONG(%d) File name too long\n", message, error);
          break;
          case ENOLCK:
          printf("%s : ENOLCK(%d) No locks available\n", message, error);
          break;
          case ENOSYS:
          printf("%s : ENOSYS(%d) Function not implemented\n", message, error);
          break;
          case ENOTEMPTY:
          printf("%s : ENOTEMPTY(%d) Directory not empty\n", message, error);
          break;
          case EILSEQ:
          printf("%s : EILSEQ(%d) Invalid multibyte sequence\n", message, error);
          break;
          default:
          printf("%s : (%d) : Socket error\n", message, error);
          break;
        }
        exit(1);
      }
    
      /*
      * funzione thread produttore
      * gestisco l'accesso alla sezione critica con tre semafori :
      * semaforo 'vuote' inizializzato a MAX
      * semaforo 'piene' inizializzato a 0
      * semaforo mutex inizializzato a 1
      * se ci sono celle vuote in cui poter scrivere (sem_wait(vuote))
      * e nessun altro sta accedendo alla sezione critica (sem_wait(mutex))
      * allora controllo se non sto scrivendo in una cella già occupata
      * altrimenti genero un numero random e lo metto nella cella
      * infine rilascio la risorsa ad un altro thrad (sem_post(mutex))
      * e indico che una cella è stata riempita e ed è pronta per essere prelevata (sem_post(piene))
      */
      void* threadProduttore (void* parametri){
        int i = 0;
        while (i++ < MAX){
          int random = rand()%10;
          sem_wait (vuote);
          sem_wait (mutex);
          if (buffer[indexP] != '\0'){
            printf("Ops... Sto scrivendo una cella vuota\n");
            exit(1);
          }
          buffer[indexP] = random + '0';
          indexP = (indexP+1) % BUFFER_SIZE;
          sem_post (mutex);
          sem_post (piene);
        }
        pthread_exit (NULL);
      }
    
      /*
      * funzione thread consumatore
      * gestisco l'accesso alla sezione critica con tre semafori :
      * semaforo 'vuote' inizializzato a MAX
      * semaforo 'piene' inizializzato a 0
      * semaforo mutex inizializzato a 1
      * se ci sono celle piene da cui poter leggere (sem_wait(piene))
      * e nessun altro sta accedendo alla sezione critica (sem_wait(mutex))
      * allora controllo se non sto leggendo da una cella vuota
      * altrimenti stampo su terminale il valore della cella da cui sto leggendo
      * quindi azzero la cella da cui ho letto
      * infine rilascio la risorsa ad un altro thrad (sem_post(mutex))
      * e indico che una cella è stata letta e svuotata ed è pronat per essre occupata nuovamente (sem_post(vuote))
      */
      void* threadConsumatore (void* parametri){
        int i = 0;
        while (i++ < MAX){
          char res;
          sem_wait (piene);
          sem_wait (mutex);
          res = buffer[indexC];
          if (res == '\0'){
            printf("Ops... Sto leggendo una cella vuota\n");
            exit(1);
          }
          printf("Thread consumatore legge:\n\tBuffer[%d] : %c\n", indexC, res);
          buffer[indexC] = '\0';
          indexC = (indexC+1) % BUFFER_SIZE;
          sem_post (mutex);
          sem_post (vuote);
        }
        pthread_exit (NULL);
      }
    
      /*
      * compilare il proramma con gcc nomeFile.c -o testThread
      * invocare il programma nel seguente modo :
      * "./testThread" : per invocare il programma con numero di produttori e consumatori settato di default pari a 1 e 1
      * "./testThread -s numeroPari" : per invocare il programma con numero di produttori e consumatori settati come si preferisce, il numero immesso deve essere pari in quanto ci devono essere n produttori e n consumatori
      * inizialmente controllo se il programma è stato invocato correttamente, invocando in caso una funzione per mostrare le istruzioni di invocazione
      * inizializzo l'array di dimensone BUFFER_SIZE con '\0'
      * creo i tre semafori :
      * semaforo 'vuote' inizializzato a MAX
      * semaforo 'piene' inizializzato a 0
      * semaforo mutex inizializzato a 1
      * in base a quanti produttori e consumatori si è scelto di avere creo i relativi pthread_t
      * metà di essi saranno assegnati alla funzione che produce mentre l'altra metà alla funzione che consuma
      * controllo che le creazioni dei thread siano andate a buon fine e gestisco eventuali errori con la funzione errorHandler
      * infine aspetto che tutti i thread concludano il loro lavoro
      * chiudo e rimuovo tutti e tre i semafori
      */
      int main (int argc, char* argv[]){
        int i;
        int errore;
        int numeroThread = 2;
        pthread_t* threadID;
        indexP = 0;
        indexC = 0;
        if (argc != 1 &amp;&amp; argc != 3) {
          die();
        }
        if (argc == 3) {
          if (!strcmp (argv[1], "-s")){
            numeroThread = atoi (argv[2]);
            if (((numeroThread % 2) != 0) || (numeroThread == 0)) {
              die();
            }
          } else {
            die();
          }
        }
        memset (buffer, 0, BUFFER_SIZE);
        mutex = sem_open ("mutex", O_CREAT, 0700, 1);
        piene = sem_open ("piene", O_CREAT, 0700, 0);
        vuote = sem_open ("vuote", O_CREAT, 0700, BUFFER_SIZE);
        printf("La dimensione del Buffer è : %d\n", BUFFER_SIZE);
        printf("Produttori : %d\nConsumatori : %d\n", numeroThread/2, numeroThread/2);
        sleep(2);
        threadID = (pthread_t*) malloc ( numeroThread * sizeof (pthread_t));
        for (i = 0; i < (numeroThread / 2); i++){
          errore = pthread_create (threadID+i, NULL, threadProduttore, NULL);
          if (errore != 0){
            errorHandler ("errore creazione threadProduttore", errore);
          }
        }
        for (i = (numeroThread / 2); i < numeroThread; i++){
          errore = pthread_create (threadID+i, NULL, threadConsumatore, NULL);
          if (errore != 0){
            errorHandler ("errore creazione threadConsumatore", errore);
          }
        }
        for (i = 0; i < numeroThread; i++){
          errore = pthread_join (*(threadID+i), NULL);
          if (errore != 0){
            errorHandler ("errore join", errore);
          }
        }
        printf ("Tutti i thread hanno concluso\n");
        sem_close (mutex);
        sem_unlink ("mutex");
        sem_close (piene);
        sem_unlink ("piene");
        sem_close (vuote);
        sem_unlink ("vuote");
        return 0;
      }
    
  4. Le tre migliori soluzioni (in ordine), anche in termini di commenti, sono:

    1. Pompili

    2. Zen (potevi spiegare in modo più esauriente la tua soluzione)

    3. Battistich (carina la soluzione del bilanciamento ma potevi spiegarla. In generale potevi commentare un po’ di più)

    Complimenti! Avrete un bonus di +1 sul voto dello scritto della parte B. (Ricordatemelo alla registrazione!)

  5. 
    //ESERCIZIO 1: SEZIONE CRITICA
    
    #include <pthread.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <sys/syscall.h>
    #include <semaphore.h>
    
    
    
    //COMPILAZIONE : gcc es1.c -O0 -pthread -o out1
    
    
    
    /*
    Creo 2 thread, ciascuno dei quali, grazie al semaforo "gestor" dopo richiamato, andrà
    ad incrementare alternatamente la variabile globale "same_var" fino ad arrivate ad un 
    totale di 1000000, stampando alla fine il risultato atteso dagli incrementi e quello
    realmente ottenuto.
    */
    
    
    
    
    //Questo è il semaforo visibile per entrambi i thread che verranno creati.
    sem_t gestor;
    
    
    //Questa è la variabile che i thread dovranno incrementare.
    long int same_var;
    
    
    
    
    
    // stampa gli errori e termina
    void die(char * printer, int ep) {
        printf("%s [%i]\n",printer,ep);
        exit(1);
    }
     
    
    
    void * thread_exec(void *inpNot)//Questo è il codice eseguito dai thread
    {
        int u;
        printf("Io sono %lu (%ld)\n",pthread_self(),syscall(SYS_gettid)); //Informo l'utente di quale thread stia eseguendo questo codice(id reale e di libreria).
        for(u=0; u<1000000;u++)		
        {                                //Ciclo un milione di volte e incrementando altrettante volte la variabile same_var, stando attento 
            sem_wait(&gestor);          //che durante l'incremento il semaforo "gestor" abbia bloccato l'altro thread.
    		
            same_var++;  
    
            sem_post(&gestor);
        }
    	
        pthread_exit(NULL); //faccio terminare questo thread
    }
    
    
    int main()
    {
        pthread_t tid[2]; //Questo è l'array che conterrà l'id dei thread(tid).
        int i, err;
        sem_init(&gestor, 0, 1);//Inizializzo il semaforo a 1(verde) e impongo la condivisione per thread.
    
        for (i=0;i<2;i++)                                                //In questo ciclo for vengono creati due thread, quello principale,che continuerà
            if (err=pthread_create(&tid[i],NULL,thread_exec,NULL))      //lungo l'ambiente del main, e uno secondario che andrà ad eseguire thread_exec.
                die("ERRORE CREAZIONE THREAD.\n",err);
    
    
        for (i=0;i<2;i++)                                //"Joino" i due thread creati, permettendo a questa task di aspettare i suoi thread senza
            if (err=pthread_join(tid[i],NULL))          //che raggiunga la destroy del semaforo prima del tempo giusto (che interromperebbe gli incrementi).
                die("JOIN FALLITA.\n",err);
    
    
        printf("%ld è il risultato, quello aspettato è 2000000\n",same_var);  //stampo il risultato ottenuto e quello atteso, per permettere di controllare
                                                                             //che il numero di incrementi effettuato dai thread sia corretto.
    	
    
        sem_destroy(&gestor);            //distruggo il semoforo ora non più necessario.
        pthread_exit(NULL);             //termino il thread.
    
    }
    
    //ESERCIZIO 2: ATTESA TRA 2 THREAD
    
    #include <pthread.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <sys/syscall.h>
    #include <semaphore.h>
    
    
    //COMPILAZIONE : gcc es2.c -O0 -pthread -o out2
    
    
    
    /*
    Vado a creare due thread e un semaforo già Rosso, dando a ciascuno di essi una sequenza di 2 stampe.
    Sicuramente, pur invertendo l'ordine di creazione dei thread, la stampa della lettera "D" avverrà
    sempre per ultima grazie allo stato iniziale del semaforo, il quale fermerà il thread che dovrà
    stampare "D" se comincerà la sua esecuzione prima dell'altro thread, o nel caso in cui l'altro thread cominciasse
    per primo la  sua esecuzione, passando per una post (non bloccante a differenza della wait) esso stamperà
    per primo gli elementi. Quindi in entrambi i casi il destino della "D" è lo stesso.
    */
    
    
    //Questo è il semaforo visibile per entrambi i thread che verranno creati.
    sem_t enter;
    
    /*
    Per testare che la "D", all'interno del codice thread "thread_First" verrà stampata per ultima
    si può eseguire diverse volte il programma, eventualmente anche scambiando di posto la creazione
    dei due thread. Si può notare che anche se venisse stampato "C" prima di "A" e "B", o "A" e "B" venissero
    stampati prima di "C", la "D" sarebbe sempre l'ultima ad essere stampata.
    
    */
    
    // stampa gli errori e termina
    void die(char * printer, int ep) {
        printf("%s [%i]\n", printer, ep);
        exit(1);
    }
     
    
    void * thread_First(void *noInput)
    {
        printf("C\n"); //Verra stampato per prima "C", oppure "A" se il thread che esegue thread_Second arriva prima.
    
        sem_wait(&enter);//Se il thread che esegue questo codice arriva per primo si blocca, perchè il semaforo è già rosso,
                        //dovendo così aspettare il sem_post dell altro thread, altrimenti se l'altro thread è arrivato prima 
                       //alla sem_post che questo thread alla wait, questo thread non si bloccherà affatto.
    
        printf("D\n");//A prescindere questa "D" verrà stampata per ultima in quanto se "First" viene eseguito per primo
                     //si bloccherà alla wait, aspettando che il post in "Second" venga eseguito prima, e quindi A e B verranno
                    //stampate sicuramente prima di "D", altrimenti se venisse eseguito thread_second per primo verranno stampate
                   //"A" e "B" per prime in assoluto, stampando comunque poi "C" e "D".
        pthread_exit(NULL);//Eseguo la exit del thread.
    }
    
    
    
    void * thread_Second(void *noInput)
    {
        printf("A\n");//Verra stampato per prima "A", oppure "C" se il thread che esegue thread_First arriva prima.
    
        sem_post(&enter);//Eseguo questa post, o prima, o dopo la wait.Nel primo caso stampa "A" per prima e pure "B",
                        //in caso contrario verrà stampata prima "C", poi "A".In ogni caso questa post permette al thread 
                       //che esegue questo codice di stampare "A" e "B" prima della "D".
    
        printf("B\n");
    
        pthread_exit(NULL);//Eseguo la exit del thread.
    }
    
    
    
    
    int main()
    {
        pthread_t tid[2]; //Questo è l'array che conterrà il "tid" dei thread
        int err1,err2;//Queste conterranno il risultato della create e poi della join, per individuare eventuali errori.
        sem_init(&enter, 0, 0);//Inizializzo il semaforo a 0(rosso) e impongo la condivisione per thread
    
    	if (err1=pthread_create(&tid[0], NULL, thread_First, NULL))//Creo il primo thread e faccio terminare il programma tramite "die" 
            die("Errore creazione thread 0.\n", err1);            //nel caso la creazione non avvenga correttamente.
    
        if (err2=pthread_create(&tid[1], NULL, thread_Second, NULL))//Creo il secondo thread e faccio terminare il programma tramite "die" 
        	die("Errore creazione thread 1.\n", err2);             //nel caso la creazione non avvenga correttamente.
    
    
        //Faccio una join di entrambi i thread una volta che avranno terminato entrambi il loro compito.
        if (err1=pthread_join(tid[0], NULL))	
            die("JOIN 0 FALLITA.\n", err1);
        if (err2=pthread_join(tid[1], NULL))	
            die("JOIN 1 FALLITA.\n", err2);
    
    
        sem_destroy(&enter);//Distruggo il semaforo.
    
        pthread_exit(NULL);//Termino del tutto il programma.
    
    }
    
    //ESERCIZIO 3: PRODUTTORE CONSUMATORE
    
    #include <pthread.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <sys/syscall.h>
    #include <semaphore.h>
    
    
    
    //COMPILAZIONE : gcc es3.c -O0 -pthread -o out3
    
    /*
    Permetto all utente di creare un buffer della dimensione che vuole, e di creare un insieme di thread
    produttori di elementi da inserie nel buffer, e un insieme di thread consumatori dei elementi inseriti
    dai primi. Per gestire senza errori la loro esecuzione ci si affida a 3 semafori, uno mutex  per permettere 
    ad un unico thread l'accesso al buffer(che sia in scrittura o lettura), e altri due che faranno da "controllori"
    per il numero di elementi inseriti o estreatti dal buffer.
    */
    
    
    
    
    sem_t full, empty, mutex;// sono i tre semafori, di cui uno Mutex
    
    int *buffer;
    int inserisci=0;
    int preleva=0;
    int MAX;
    int prodIndex=0, consIndex=0;
    
    
    
    
    
    
    // Stampa l'errore, l'identificatore dell'errore e il punto in cui il thread si è interrotto durante il ciclo interno al codice
    // dei produttori o dei consumatori.
    void die(char * printer, int ep, int d) {
        printf("%s [%i] e la d stava a %d\n", printer, ep, d);
        exit(1);
    }
    
    //Stampa l'elemento che ogni volta il thread consumatore riesce a leggere. 
    void dontDieInt(int toPrint) //Utile nel ambiente dei thread, mentre nel ambiente del main è sufficiente un semplice printf.
    {
        int i=0;
        //for(i=0 ; i < 1000; i++);//Questo ciclo serve a rallentare leggermente le varie stampe invocate dai consumatori, permettendo di
                                  //distinguerle in maniera più coerente se si sta eseguendo i cicli dei thread all infinito
                                 //(però la cosa non aiuta se il numero di thread è sproporzionato).
                                //Per provarne l'utilità porre la condizione dei while di "producer" e "user" a "1", e decommenta questo for.
        printf("%d \n",toPrint);
    }
    
    
    
     
    void * producer (void *noInp)//Questo è il codice dei thread PRODUTTORI.
    {
        while(prodIndex < 1000)
        {
            prodIndex = prodIndex + 1;
            sem_wait(&empty); //P(empty): richiede una cella vuota
            sem_wait(&mutex); //P(mutex): si assicura di essere l'unico thread ad accedere al buffer, 
                              //          evitando che altri scrivano o leggano al contempo.
            if(buffer[inserisci] != -1)//Si segnala un errore e si termina se la cella non contiene -1, in quanto indica che la cella non è vuota.
                die("Errore, diverso da -1.\n",0,prodIndex);
            buffer[inserisci] = prodIndex; //scrivo l'elemento nel buffer
            inserisci = (inserisci + 1) % MAX; //modifico l'indice per la prossima scrittura.
    
            sem_post(&mutex); //V(mutex): il produttore in azione ha finito, lascia spazio all eventuale azione di altri produttori.
            sem_post(&full); //V(full): rilascia una cella piena
        }
        pthread_exit(NULL);	          
    }
    
    
    
    
    
     
    void * user (void *noInp)//Questo è il codice dei thread CONSUMATORI.
    {
    
    	while(consIndex < 1000)
    	{
    		sem_wait(&full); //P(full): richiede una cella piena
    	    sem_wait(&mutex); //P(mutex); pone in attesa un qualunque produttore che intenda scrivere nel buffer.
    
    	    if(buffer[preleva]==-1)//Segnala un errore e termina se la cella da svuotare è già vuota.
    	    	die("trovato -1.\n", 0, consIndex);
    	    consIndex = buffer[preleva];//Prelevo l'elemento dal buffer.
    	    buffer[preleva] = -1;//"Svuoto" la cella.
    	    preleva = (preleva+1) % MAX; //Sposto l'indice per i prossimi prelievi.
    
    	    sem_post(&mutex); //V(mutex): permette ai produttori di tornare a scrivere.
    	    sem_post(&empty); //V(empty):rilascia una cella vuota
    	    dontDieInt(consIndex);//Questa funzione serve a stampare l'attuale elemento consumato.
    	}
    	pthread_exit(NULL);			     
    }
    
    
    
    
    
    
    
    
    
    
    
    
    int main()
    {
    
        pthread_t *tid;//Questo sarà l'array che conterrà il tid di tutti i thread.
        int i, j;
        int muchThread;//Qui verrà memorizzato il numero totale di thread da creare.
        int *errors;//Questo sarà l'array che conterrà i risultati di tutte le pthread_create e successivamente delle join.
    	int muchProducers, muchUsers;//Rispettivamente memorizzeranno il numero di thread Produttori e Consumatori da creare.
    
        printf("Scegli il numero di elementi del buffer:\n");
        scanf("%d", &MAX);
    
        printf("Scegli il numero di thread consumatori/produttori da creare:\n");
        scanf("%d", &muchThread);
    
        tid =(pthread_t*) malloc (sizeof(pthread_t) * muchThread);//alloco spazio all'array che conterrà
                                                             //il tid di tutti i thread che andremo a creare.
        errors =(int*) malloc (sizeof(int) * muchThread);//alloco spazio all'array che conterrà tutti i eventuali errori
                                                    //possibilmente originati dalla creazione dei thread.
    
    
        printf("ORA verrà richiesto quanti thread creare come consumatori e quanti come produttori:\n");
        printf("All aumentare dei thread avremmo una stampa non ordinata .\n\n");
    
        muchProducers = muchThread; //Rendo falsa già da subito la condizione del successivo while, costringendo
    							  //l'utente a selezionare un numero di thread produttori corretto.
        while(muchProducers >= muchThread)
        {
            printf("Scegli quanti dei %d thread saranno produttori (i restanti saranno consumatori):\n", muchThread);
            scanf("%d", &muchProducers);
            if(muchProducers >= muchThread)
                printf("Hai scelto troppi thread produttori.\n");
        }
    
    
        muchUsers=muchThread - muchProducers;
    
        printf("%d users e %d producers. \n",muchUsers, muchProducers );
        sleep(1);//stampo all'utente il numero di thread consumatori e produttori creati
    			// per controllare che sia tutto giusto fino ad ora e uso una sleep per lasciare
    		   //  tempo all'utente di controllare prima che parta il rapporto produttore consumatore.
    
    
    
    	//Creo i 3 semafori per permettere l'alternarsi dei thread nell'accesso delle celle al buffer.
        sem_init(&full, 0, 0);
        sem_init(&empty, 0, MAX);
        sem_init(&mutex, 0, 1);
        buffer=(int*) malloc (sizeof(int) * MAX);
    	
    
    
        for(j=0; j<MAX; j++)//Inizializzo tutte le celle a -1, permettendo di riconoscere gli eventuali errori tra i thread: 
            buffer[j] = -1;	//    *se la cella presa in lettura contiene -1 nasce l'errore, perche -1 indica "vuoto".
    						//	  *se la cella presa in scrittura non contiene -1 vuol dire che nella precedente lettura non è stata svuotata.
    
        for(i=0; i<muchProducers; i++)//Creo i thread produttori e controllo se nascono eventuali errori nella loro creazione.
        {
            if(errors[i]=pthread_create(&tid[i],NULL,producer, NULL))
                die("ERRORE CREAZIONE PRODUTTORE NUMERO %d\n",errors[i],0);
        }
        for(j=0 ; i<muchThread; i++)//Creo i thread consumatori e controllo se nascono eventuali errori nella loro creazione.
        {
            if(errors[i]=pthread_create(&tid[i],NULL,user,NULL))
                die("ERRORE CRREAZIONE CONSUMATORE NUMERO %d\n",errors[i],0);
            j++;
        }
    
        /*
        Le join qui di seguito sono necessarie solamente nel caso in cui il codice dei thread fosse limitato nell'
        esecuzione, in quanto se ponessimo i cicli dei Consumatori e dei Produttori a infinito le join qui
        sotto non verrebbero mai completate, non venendo eseguito il resto del codice dopo questo ciclo for.
        */
        for(i=0 ; i < muchThread; i++)
        {
            if (errors[i]=pthread_join(tid[i], NULL))	
                die("JOIN FALLITA.\n", errors[i], 0);
            printf("Terminato %d-esimo thread \n", i);//ad un incremento eccessivo del numero di thread, questa stampa potrebbe apparire 
        }                                            //in mezzo agli ultimi numeri stampati dai consumatori.
    
        //Distruggo i semafori visto che a questo punto i consumatori e i produttori non esistono più.
        sem_destroy(&mutex);
        sem_destroy(&empty);
        sem_destroy(&full);
    
        //Dealloco il buffer
        free(buffer);
        pthread_exit(NULL);
    }
    
    

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.