Un thread è un’unità di esecuzione all’interno di un processo.
Un processo può avere più thread in esecuzione, che tipicamente condividono le risorse del processo e, in particolare, la memoria.
Lo standard POSIX definisce un insieme di funzioni per la creazione e la sincronizzazione di thread.
pthread_create(pthread_t *thread, pthread_attr_t *attr, void *(*start_routine)(void *), void *arg)
thread
: un puntatore a pthread_t
, l’analogo di pid_t
. Attenzione che non necessariamente è implementato come un intero;
attr
: attributi del nuovo thread. Se non si vogliono modificare gli attributi è sufficiente passare NULL;
start_routine
il codice da eseguire. È un puntatore a funzione che prende un puntatore a void e restituisce un puntatore a void. Ricordarsi che in C il nome di una funzione è un puntatore alla funzione;
arg
eventuali argomenti da passare, NULL se non si intende passare parametri.
pthread_exit(void *retval)
termina l’esecuzione di un thread restituendo retval
. Si noti che quando il processo termina (exit
) tutti i suoi thread vengono terminati. Per far terminare un singolo thread si deve usare pthread_exit
;
pthread_join(pthread_t th, void **thread_return)
attende la terminazione del thread th
. Se ha successo, ritorna 0 e un puntatore al valore ritornato dal thread. Se non si vuole ricevere il valore di ritorno è sufficiente passare NULL come secondo parametro.
pthread_detach(pthread_t th)
se non si vuole attendere la terminazione di un thread allora si deve eseguire questa funzione che pone th
in stato detached: nessun altro thread potrà attendere la sua terminazione con pthread_join
e quando terminerà le sue risorse verranno automaticamente rilasciate (evita che diventino thread “zombie”).
Si noti che pthread_detach
non fa sì che il thread rimanga attivo quando il processo termina con exit
.
pthread_t pthread_self()
ritorna il proprio thread id.
ATTENZIONE: questo è l’ID della libreria phread e non l’ID di sistema. Per visualizzare l’ID di sistema si può usare, in Linux, syscall(SYS_gettid)
.
#include <stdio.h> #include <stdlib.h> #include <pthread.h> #include <unistd.h> #include <sys/syscall.h> /* For SYS_xxx definitions */ void * codice_thread(void * a) { pthread_t tid; int ptid; tid = pthread_self(); // library tid ptid = syscall(SYS_gettid); // tid assegnato dal SO (funziona solo in Linux) printf("Sono il thread %lu (%i) del processo %i\n",tid,ptid,getpid()); sleep(1); pthread_exit(NULL); } int main() { pthread_t tid[2]; int i,err; // crea i thread (ritorna 0 quando ha successo, vedere il man!) for (i=0;i<2;i++) { if (err=pthread_create(&tid[i],NULL,codice_thread,NULL)) { printf("errore create [%i]\n",err); exit(EXIT_FAILURE); } } // attende i thread. Non si legge il valore di ritorno (secondo parametro NULL) for (i=0;i<2;i++) { if (err=pthread_join(tid[i],NULL)) { printf("errore join [%i]\n",err); exit(EXIT_FAILURE); } } printf("I thread hanno terminato l'esecuzione correttamente\n"); }
Il programma va compilato con l’opzione -pthread
per aggiungere macro e codice utile alla gestione dei thread.
> gcc test1.c -pthread -o test1 <=== opzione -pthread !!
> ./test1
Sono il thread 139816621209344 (221) del processo 220
Sono il thread 139816612816640 (222) del processo 220
I thread hanno terminato l'esecuzione correttamente
Si possono notare gli ID di libreria (unsigned long) e tra parentesi quelli di sistema visualizzabili con ps -L
. Provare da un altro terminale (o lanciando test1 &
) mentre i thread sono in esecuzione:
$ ps -AL | grep test1
220 220 pts/0 00:00:00 test1
220 221 pts/0 00:00:00 test1
220 222 pts/0 00:00:00 test1
Provare a “distaccare” uno dei thread e osservare l’errore restituito dalla join.
ATTENZIONE: essendo una libreria esterna, gli errori non possono essere visualizzati con perror
(che stampa gli errori di sistema). Consultare il manuale delle chiamate a libreria per vedere i possibili errori restituiti.
In ubuntu 64 bit, i codici di errore si trovano in /usr/include/asm-generic/errno-base.h
.
argv[1]
e argv[2]
). I due thread calcolano il quadrato del numero intero e il thread principale, infine, stampa la somma dei due valori ottenuti.
Fare attenzione: la memoria è condivisa quindi si deve passare ai 2 thread l’indirizzo di una zona di memoria “riservata” in modo da evitare interferenze.
num[2]
nel main
atoi(argv[1])
e atoi(argv[2])
in num[0]
e num[1]
num[0]
e num[1]
ai due thread (è necessario un cast a void *
)num[0]
e num[1]
(è necessario un cast a int *
)num[0]+num[1]
, le join infatti assicurano che i thread abbiamo già computato la somma (lasciate la sleep
per verificare che il main attende i risultati corretti)
Variante: se volete tenere input e output dei thread distinti potete usare una struct
con due campi.
Creare 2 thread che aggiornano ripetutamente (in un ciclo for) una variabile condivisa count
, dichiarata fuori dalla funzione main
, per un numero elevato di volte (ad esempio 1000000). Stampare il valore finale, dopo le join, per osservare eventuali incrementi perduti dovuti a interferenze.
NOTA: il compilatore potrebbe ottimizzare il codice e fare, ad esempio, un unico incremento di 1000000 sulla variabile count
o incrementare count
senza usare registi! In questi casi si potrebbero perdere direttamente 1000000 incrementi o non non avere interferenze! Per evitare ottimizzazioni dare a gcc l’opzione -O0 (che dovrebbe essere di default). Per sperimentare ottimizzazioni provare con -O oppure -O3.