- Semafori in C: cosa sono, come funzionano e la relazione con i mutex -
 
COSA SERVE PER QUESTO TUTORIAL
Download | Chiedi sul FORUM | Glossario conoscenza del linguaggio C e delle nozioni base per l'utilizzo dei thread con pthread
Il concetto di semaforo

I SEMAFORI
Cosa sono i semafori e come vanno utilizzati.

Per il multi-threading le librerie POSIX pthread offrono principalmente due strumenti per la sincronizzazione: i semafori e i mutex. In questo articolo ci occuperemo dei semafori per capire esattamente cosa sono e in che caso possono servire. Esplicitiamo la metafora del semaforo.
Immaginiamo di avere un semaforo, che gestisce il numero di macchine che possono entrare in una circuito. Per esempio supponiamo che possano entrare solo 5 macchine contemporaneamente, allora il semaforo sarà inizialmente verde e di fianco immaginiamo che vi sia scritto 5, per indicare il numero di macchine che possono passare, inizialmente il massimo dato che la pista è ancora vuota. Ogni volta che una macchina arriva, questa dovrà controllare il semaforo: se è verde può passare e il contatore del semaforo verrà decrementato di uno, se è rosso invece dovrà stare in attesa del verde. Quando il contatore raggiunge lo zero il semaforo diventa rosso, il contatore verrà poi incrementato ogni volta che qualcuno ha terminato il proprio giro in pista.
Ad esempio: arriva la prima macchina è il semaforo è su verde-5, la macchina passa e il semaforo passa a verde-4, passa una seconda macchina e diventa verde-3, una volta passata la quinta macchina il semaforo ha esaurito il numero di posti disponibili in pista ed entra quindi nello stato rosso-0. La sesta macchina arriva al semaforo e lo trova rosso: si mette in attesa. Quando una qualunque delle 5 macchine precedenti (non per forza la prima) ha completato il circuito il semaforo si accorge che si è liberato uno spazio in pista e quindi il suo stato passerà a verde-1, tuttavia vi era la sesta macchina in attesa, che vorrà quindi passare immediatamente portandolo nuovamente in rosso-0.
I semafori di pthread (ma il concetto vale anche per altre librerie) sono essenzialmente costituiti dal semaforo, ovvero un contatore numerico (contenuto in una struttura di tipo sem_t), e le funzioni sem_wait e sem_post. Il contatore numerico viene inizializzato sul numero di posti disponibili in pista. La funzione sem_wait corrisponde nel nostro esempio all'arrivo di una macchina (ovvero un thread) al semaforo che deve decidere se passare e decrementare il contatore, nel caso sia verde, o restare in attesa, nel caso sia rosso. La funzione sem_post corrisponde invece all'uscita dalla pista di una macchina (thread), in sostanza essa incrementerà il contatore del semaforo di uno, portandolo su verde se necessario e facendo quindi partire un'eventuale macchina in attesa. Vediamo un esempio di implementazione del circuito:

		
#include <unistd.h> 
#include <stdlib.h>
#include <semaphore.h>
#include <pthread.h>
#include <stdio.h>

#define MACCHINE 10
#define POSTIPISTA 5
#define MAX_WAIT 10

sem_t semaforo;

// Restituisce il valore del semaforo
int valoreSemaforo(void) {
    int valore = -1;
    sem_getvalue(&semaforo, &valore);
    return valore;
}

void *macchina(void *arg) {
    // Controlliamo il semaforo:
    // se è verde passiamo e ne decrementiamo il valore,
    // se è rosso attendiamo che diventi verde.
    printf("Macchina %d al semaforo, valore: %d\n", (int) arg, valoreSemaforo());
    sem_wait(&semaforo);
    printf("Macchina %d passata al semaforo, valore: %d\n", (int) arg, valoreSemaforo());
    
    // La macchina fa il proprio giro in una quantità di tempo casuale.
    sleep((rand() % MAX_WAIT)+1);

    // Esce dalla pista, incrementa il semaforo e sblocca
    // un altro thread, se ve ne sono in attesa.
    sem_post(&semaforo);
    printf("Macchina %d uscita dal circuito, valore: %d\n", (int) arg, valoreSemaforo());
    return NULL;
}

int main(int argc, char *argv[]) {
    int c1;
    pthread_t macchine[MACCHINE];
    
    // Inizializziamo il semaforo sui posti disponibili
    // in pista
    sem_init(&semaforo, 0, POSTIPISTA);
    
    // Facciamo entrare una macchina ogni secondo
    for(c1=0;c1<MACCHINE;c1++) {
        pthread_create(&macchine[c1], NULL, macchina, (void*) c1);
        sleep(1);
    }

    // Attendiamo la conclusione di tutti i thread
    for(c1=0;c1<MACCHINE;c1++)
        pthread_join(macchine[c1], NULL);

    return 0;
}

REALIZZAZIONE DI UN MUTEX TRAMITE I SEMAFORI
Come implementare un mutex utilizzando un solo semaforo.

Nell'esempio precedente abbiamo in sostanza realizzato un mutex, la cui sezione critica è il giro in pista, ma che, al contrario dei normali mutex, permette ad un numero arbitrario di thread (nell'esempio 5) di accedere alla sezione critica. Se avessimo impostato POSTIPISTA su 1, avremmo avuto l'equivalente di un mutex:


#include <semaphore.h>
#include <pthread.h>
#include <stdio.h>

#define THREADCOUNT 50

sem_t mutex;
int count;

void *myThread(void *arg) {
    int c1;

    sem_wait(&mutex);
    
    // Inizio sezione critica
    // Nessun rischio di scritture incrociate
    for(c1=0;c1<100;c1++)
        count++;
    // Fine sezione critica

    sem_post(&mutex);
    return NULL;
}

int main(int argc, char *argv[]) {
    int c1;
    pthread_t thread[THREADCOUNT];
    
    // Il semaforo deve essere inizializzato a 1
    // perché si comporti come un mutex
    sem_init(&mutex, 0, 1);
    
    // Avviamo i thread
    for(c1=0;c1<THREADCOUNT;c1++)
        pthread_create(&thread[c1], NULL, myThread, NULL);

    // Attendiamo la conclusione di tutti i thread
    for(c1=0;c1<THREADCOUNT;c1++)
        pthread_join(thread[c1], NULL);
    
    printf("Totale: %d\n", count);

    return 0;
}


<< INDIETRO by VeNoM00