Nella parte I della serie Linux Threads, abbiamo discusso vari aspetti relativi ai thread in Linux.
In questo articolo ci concentreremo su come viene creato e identificato un thread. Presenteremo anche un esempio di programma C funzionante che spiegherà come eseguire la programmazione thread di base.
Serie Linux Threads:parte 1, parte 2 (questo articolo), parte 3.
Identificazione del filo
Proprio come un processo viene identificato tramite un ID processo, un thread viene identificato da un ID thread. Ma è interessante notare che la somiglianza tra i due finisce qui.
- Un ID processo è univoco nel sistema, mentre un ID thread è univoco solo nel contesto di un singolo processo.
- Un ID processo è un valore intero ma l'ID thread non è necessariamente un valore intero. Potrebbe benissimo essere una struttura
- Un ID processo può essere stampato molto facilmente mentre un ID thread non è facile da stampare.
I punti precedenti danno un'idea della differenza tra un ID processo e un ID thread.
L'ID del thread è rappresentato dal tipo 'pthread_t'. Come abbiamo già discusso, nella maggior parte dei casi questo tipo è una struttura, quindi deve esserci una funzione in grado di confrontare due ID thread.
#include <pthread.h> int pthread_equal(pthread_t tid1, pthread_t tid2);
Quindi, come puoi vedere, la funzione precedente prende due ID thread e restituisce un valore diverso da zero se entrambi gli ID thread sono uguali oppure restituisce zero.
Un altro caso può verificarsi quando un thread vorrebbe conoscere il proprio ID thread. In questo caso la seguente funzione fornisce il servizio desiderato.
#include <pthread.h> pthread_t pthread_self(void);
Quindi vediamo che la funzione 'pthread_self()' viene utilizzata da un thread per stampare il proprio ID thread.
Ora, ci si chiederebbe il caso in cui sarebbero necessarie le due funzioni precedenti. Supponiamo che ci sia un caso in cui un elenco di collegamenti contenga dati per thread diversi. Ogni nodo nell'elenco contiene un ID thread e i dati corrispondenti. Ora ogni volta che un thread tenta di recuperare i suoi dati dall'elenco collegato, prima ottiene il proprio ID chiamando 'pthread_self()' e poi chiama 'pthread_equal()' su ogni nodo per vedere se il nodo contiene dati per esso o meno .
Un esempio del caso generico discusso sopra potrebbe essere quello in cui un thread principale ottiene i lavori da elaborare e quindi li inserisce in un elenco di collegamenti. Ora i singoli thread di lavoro analizzano l'elenco collegato ed estraggono il lavoro loro assegnato.
Creazione del filo
Normalmente quando un programma si avvia e diventa un processo, inizia con un thread predefinito. Quindi possiamo dire che ogni processo ha almeno un thread di controllo. Un processo può creare thread aggiuntivi utilizzando la seguente funzione:
#include <pthread.h> int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr, void *(*start_rtn)(void), void *restrict arg)
La funzione di cui sopra richiede quattro argomenti, discutiamo prima un po' su di essi:
- Il primo argomento è un indirizzo di tipo pthread_t. Una volta che la funzione è stata chiamata con successo, la variabile il cui indirizzo è passato come primo argomento conterrà l'ID del thread appena creato.
- Il secondo argomento può contenere alcuni attributi che vogliamo che contenga il nuovo thread. Potrebbe essere prioritario ecc.
- Il terzo argomento è un puntatore a una funzione. Questo è qualcosa da tenere a mente che ogni thread inizia con una funzione e che l'indirizzo della funzione viene passato qui come terzo argomento in modo che il kernel sappia da quale funzione iniziare il thread.
- Poiché la funzione (il cui indirizzo è passato nel terzo argomento sopra) può anche accettare alcuni argomenti, quindi possiamo passare questi argomenti sotto forma di puntatore a un tipo void. Ora, perché è stato scelto un tipo vuoto? Questo perché se una funzione accetta più di un argomento, questo puntatore potrebbe essere un puntatore a una struttura che potrebbe contenere questi argomenti.
Un esempio pratico di thread
Di seguito è riportato il codice di esempio in cui abbiamo provato a utilizzare tutte e tre le funzioni discusse sopra.
#include<stdio.h> #include<string.h> #include<pthread.h> #include<stdlib.h> #include<unistd.h> pthread_t tid[2]; void* doSomeThing(void *arg) { unsigned long i = 0; pthread_t id = pthread_self(); if(pthread_equal(id,tid[0])) { printf("\n First thread processing\n"); } else { printf("\n Second thread processing\n"); } for(i=0; i<(0xFFFFFFFF);i++); return NULL; } int main(void) { int i = 0; int err; while(i < 2) { err = pthread_create(&(tid[i]), NULL, &doSomeThing, NULL); if (err != 0) printf("\ncan't create thread :[%s]", strerror(err)); else printf("\n Thread created successfully\n"); i++; } sleep(5); return 0; }
Quindi ciò che fa questo codice è :
- Utilizza la funzione pthread_create() per creare due thread
- La funzione di partenza per entrambi i thread è mantenuta invariata.
- All'interno della funzione 'doSomeThing()', il thread utilizza le funzioni pthread_self() e pthread_equal() per identificare se il thread in esecuzione è il primo o il secondo come creato.
- Inoltre, all'interno della stessa funzione 'doSomeThing()' viene eseguito un ciclo for in modo da simulare un lavoro che richiede tempo.
Ora, quando viene eseguito il codice precedente, il seguente è stato l'output:
$ ./threads Thread created successfully First thread processing Thread created successfully Second thread processing
Come si vede nell'output, viene creato il primo thread e inizia l'elaborazione, quindi viene creato il secondo thread e quindi inizia l'elaborazione. Bene, un punto da notare qui è che l'ordine di esecuzione dei thread non è sempre fisso. Dipende dall'algoritmo di pianificazione del sistema operativo.
Nota:l'intera spiegazione in questo articolo viene eseguita sui thread Posix. Come si può capire dal tipo, il tipo pthread_t sta per thread POSIX. Se un'applicazione desidera verificare se i thread POSIX sono supportati o meno, l'applicazione può utilizzare la macro _POSIX_THREADS per il test in fase di compilazione. Per compilare un codice contenente chiamate alle API posix, utilizza l'opzione di compilazione '-pthread'.