Continuiamo la trattazione della programmazione concorrente presentando un altro listato recuperato su internet che possa stavolta mostrarci come funzionano e lavorano i semafori.
I semafori sono una struttura dati che permette la gestione della mutua esclusione tra processi o nel nostro caso tra threads dello stesso processo. Informazioni più complete sui semafori sono disponibili su wikipedia. Noi ci limiteremo ad esaminare del codice sorgente.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 | #include <stdio.h> #include <unistd.h> #include <semaphore.h> #include <pthread.h> /*! mainpage Esempio Semafori con Pthreads section intro Introduzione Esempio di utilizzo semafori date 10/05/2006 version 23.24 04/07/2009 author A.Dal Palu */ static sem_t s1,s2; /// semafori /*! brief Codice del thread1 Il thread fa Ping e fa UP sul semaforo dell`altro thread */ void *thread1(void * arg) { int i; for (i=0;i<10;i++){ sem_wait(&s1); printf("Pingn"); sem_post(&s2); } printf("T1 exitn"); pthread_exit (0); } /*! brief Codice del thread2 Il thread fa Pong e fa UP sul semaforo dell`altro thread */ void *thread2(void * arg) { int i; for (i=0;i<10;i++){ sem_wait(&s2); printf("Pongn"); sem_post(&s1); } printf("T2 exitn"); pthread_exit (0); } int main() { pthread_t tid1,tid2; void * ret; sem_init(&s1,0,1); sem_init(&s2,0,0); if (pthread_create(&tid1, NULL, thread1, NULL) < 0) { fprintf (stderr, "pthread_create error for thread 1n"); exit (1); } if (pthread_create(&tid2, NULL, thread2, NULL) < 0) { fprintf (stderr, "pthread_create error for thread 2n"); exit (1); } pthread_join (tid1, &ret); pthread_join (tid2, &ret); printf("Exitn"); } |
Il programma è composto da tre funzioni:thread1, thread2 e il main.
Una volta inseriti gli include e inizializzato le variabili, i semafori, possiamo procedere allo studio del main.
Il main crea i thread, e una variabile d’appoggio chiamata ret, che è un puntatore a void. Dopodichè inizializza i semafori creati e i thread stessi.
Effettua anche il join dei thread e alla fine termina.
La funzione thread1, invece, effettua un altro tipo di operazioni.
La funzione sem_wait sospende il thread chiamante fintanto che il valore del semaforo puntato dall’argomento è diverso da zero. Viene inoltre decrementato automaticamente, ed atomicamente, il contatore. Questo significa che settando il valore del semaforo ad 1 ed effettuando questa chiamata il processo non si arresterà.
Successivamente viene stampato il ping e dopo parte la sem_post.
Al contrario della precedente questa funzione semplicemente incrementa il valore del semaforo passato come parametro. Qualora questo semaforo avesse già raggiunto il massimo numero consentito viene ritornato -1 mentre la variabile ERRNO viene settata ad EINVAL. In caso di successo, invece, viene restituito 0.
Esaminiamo dei possibili scenari di compilazione.
Il codice per come è scritto se viene compilato ed eseguito, restituisce un output del genere:
Ping
Pong
Ping
Pong
Ping
Pong
Ping
Pong
Ping
Pong
…
T1 exit
Pong
T2 exit
Exit
Questo perchè? Perchè il primo dei due semafori ha come valore iniziale 1. Se si modificano i valori reciproci dei semafori, la situazione cambia non poco.
Ricordiamo intanto quali sono le operazioni che vengono fatte dalle funzioni. All’interno della funzione thread1, che è la prima ad essere invocata, la sem wait diminuisce il valore del semaforo, che non è 0, e per questo il thread non si blocca. Esegue quello che deve eseguire, e invoca la sem post sull’altro thread, che passa da 0 (valore iniziale) a 1.
Il gioco si ripete per il thread 2, proprio come il ping pong
Cosa succede nei casi in cui:
1) I due semafori partono entrambi da 0
2) Il primo parte da 0 e il secondo da 1
3) Partono entrambi da 1
4) Il primo ha un valore molto superiore al secondo
5) Entrambi partono da N
Esaminiamo caso per caso, prima logicamente, poi compilando e vedendo se avevamo ragione o meno…
Se T1 e T2 partono da 0 cosa succede? Viene sempre invocata thread1 per prima, che invoca la wait che incontra un semaforo con valore 0 e blocca il thread. Il quanto finisce e viene fatto lavorare il thread2, che non può non avere lo stesso destino perchè anche lui parte da 0, morale della favola: Deadlock!
Se T1 parte da 0 e T2 parte da 1 cosa succede? Semplicemente il flusso si inverte, e si parte con Pong, piuttosto che con Ping.
Pong
Ping
Pong
Ping
Pong
Ping
Pong
Ping
Pong
Ping
…
T1 exit
T2 exit
Exit
Questo perchè PRIMA viene eseguita una wait su un semaforo a valore 0.
Se T1 parte da 1 e T2 parte da 1 cosa succede? Non cambia nulla rispetto al partire t1 con 0 e t2 con 0, in quanto comunque t1 avrebbe incrementato t2 prima di terminare la sua esecuzione per cui alla fine, il comportamento è lo stesso.
Se t1 è molto maggiore di t2 in valore iniziale? Semplicemente si vedranno tanti PING consecutivi in base al numero di partenza del semaforo, perchè il ciclo chiamerà sem_wait decrementando un numero che non diventerà 0 prima delle N volte impostate inizialmente. Dopo il ciclo continuerà con un ping pong, fintantochè non si esaurisce il ciclo del primo thread, che muore, e ci saranno da allora solo pong.
Se t1 e t2 partono da N cosa succede? Caso da considerarsi identico al precedente.
