...ti spiego: io sono il thread A e tu sei il B... |
#include <stdio.h> #include <pthread.h> #include <string.h> #include <unistd.h> // creo un nuovo tipo per passare dei dati ai thread typedef struct _tdata { int index; // thread index int *comdata; // dato comune ai thread pthread_mutex_t *lock; // mutex comune ai thread } tdata; // prototipi locali void* tMyThread(void *arg); // funzione main() int main(int argc, char* argv[]) { int error; // init mutex pthread_mutex_t lock; if ((error = pthread_mutex_init(&lock, NULL)) != 0) { printf("%s: non posso creare il mutex (%s)\n", argv[0], strerror(error)); return 1; } // init threads pthread_t tid[2]; tdata data[2]; int comdata = 0; for (int i = 0; i < 2; i++) { // set data del thread e crea il thread data[i].index = i; data[i].comdata = &comdata; data[i].lock = &lock; if ((error = pthread_create(&tid[i], NULL, &tMyThread, (void *)&data[i])) != 0) printf("%s: non posso creare il thread %d (%s)\n", argv[0], i, strerror(error)); } // join thread e cancella mutex pthread_join(tid[0], NULL); pthread_join(tid[1], NULL); pthread_mutex_destroy(&lock); // exit printf("%s: thread terminati: comdata=%d\n", argv[0], comdata); return 0; } // thread routine void* tMyThread(void *arg) { // ottengo i dati del thread con un cast (tdata*) di (void*) arg tdata *data = (tdata *)arg; // thread loop printf("thread %d partito\n", data->index); int i = 0; for (;;) { // lock mutex pthread_mutex_lock(data->lock); // incrementa comdata (*data->comdata)++; // unlock mutex pthread_mutex_unlock(data->lock); // test counter per eventuale uscita dal loop if (++i >= 100) { // esce dal loop break; } // thread sleep (10 ms) usleep(10000); } // il thread esce printf("thread %d finito\n", data->index); return NULL; }
Ok, come vedete è ampiamente commentato e quindi è auto-esplicativo, per cui non mi dilungherò sulle singole istruzioni e/o gruppi di istruzioni (leggete i commenti! sono li per quello!), ma aggiungerò, solo, qualche dettaglio strutturale. Supponendo che già sappiate cosa sono e a cosa servono i thread (se no leggetevi prima qualche guida introduttiva, in rete ce ne sono di ottime) il flusso del codice è evidente: prima bisogna creare un mutex (con pthread_mutex_init()) per sincronizzare i thread che useremo, poi bisogna inizializzare i dati da passare ai thread e creare (con pthread_create()) i due thread del nostro esempio (init dati e creazione li ho messi in un loop di 2, ma si potevano anche scrivere ripetendo due volte i passi, ovviamente). Infine il main() si mette in attesa (con pthread_join()) della terminazione dei thread e, quando sono terminati, distrugge il mutex (con pthread_mutex_destroy()) ed esce.
Come si nota pthread_create() ha quattro parametri, che sono (nell'ordine): un pointer a un thread descriptor che identifica univocamente il thread creato, un pointer a un contenitore di attributi del thread da creare, un function pointer alla funzione che esegue il thread e, infine, un pointer all'unico argomento che si può passare alla funzione suddetta. In particolare, nel nostro esempio (semplice semplice), ho usato gli attributi di default (usando NULL per il secondo parametro), e ho creato (con typedef) un nuovo tipo ad-hoc per passare più parametri alla funzione che esegue il thread, sfruttando il fatto che l'argomento di default è un void* che si può facilmente trasformare (con una operazione di cast) in qualsiasi tipo complesso (nel nostro caso nel nuovo tipo tdata).
In questo esempio i due thread creati eseguono la stessa funzione, che ho chiamato tMyThread() (ma avrebbero anche potuto eseguire due funzioni completamente differenti: in questo caso, ovviamente, avrei dovuto scrivere una tMyThread1() e una tMyThread2()). Il flusso della funzione è molto semplice: prima esegue un cast sull'argomento arg per poter usare i dati del tipo tdata, poi entra in un classico thread-loop infinito con uscita forzata: nel nostro caso esce quando l'indice i arriva a 100, ma in un caso reale si potrebbe, per esempio, forzare l'uscita solo in caso di errore. Notare che il thread-loop usa una sleep di 10 ms (usando usleep()): provate a dimenticarvi di mettere la sleep in un thread-loop veramente infinito e vedrete i salti di gioia che farà la CPU del vostro PC!
Come si nota il tipo tdata contiene un indice tipico del thread (nel nostro caso è 0 o 1) e i pointer ai due dati comuni (locali al main()) che sono comdata e lock. Quindi cosa esegue il thread-loop? Visto che è un esempio semplice, si limita a incrementare il dato comune comdata inizializzato nel main() e lo fa in maniera sincronizzata usando pthread_mutex_lock() e pthread_mutex_unlock() sul mutex comune lock: questo serve per evitare che i due thread accedano contemporaneamente a comdata.
Compilando con GCC su macchina Linux (ovviamente) ed eseguendo, il risultato è:
aldo@ao-linux-nb:~/blogtest$ gcc thread.c -o thread -pthread aldo@ao-linux-nb:~/blogtest$ ./thread thread 0 partito thread 1 partito thread 1 finito thread 0 finito ./thread: thread terminati: comdata=200
Che è quello sperato. Nel prossimo post parleremo di una interfaccia alternativa ai POSIX Threads. E, come sempre, vi raccomando di non trattenere il respiro nell'attesa...
Ciao e al prossimo post!
P.S.
Come ben sapete questo è un blog di programmazione con un anima cinefila, per cui vi segnalo (con grande tristezza) che il mese scorso ci ha lasciati un grande maestro. R.I.P., George.
Nessun commento:
Posta un commento