Come creare un thread Linux in C

Come creare un thread Linux in C

Su Linux, puoi creare e gestire thread in C/C++ usando la libreria thread POSIX (pthread). A differenza di altri sistemi operativi, c’è poca differenza tra un thread e un processo in Linux. Ecco perché Linux fa spesso riferimento ai suoi thread come processi leggeri.

Utilizzando la libreria pthread, puoi creare thread, attendere che terminino e terminarli in modo esplicito.

La storia dell’utilizzo dei thread su Linux

Prima della versione 2.6 di Linux, l’implementazione del thread principale era LinuxThreads. Questa implementazione presentava limiti significativi in ​​termini di prestazioni e operazioni di sincronizzazione. Un limite al numero massimo di thread che potevano essere eseguiti li limitava a migliaia.

Nel 2003, un team guidato da sviluppatori di IBM e RedHat è riuscito a rendere disponibile il progetto Native POSIX Thread Library (NPTL). È stato introdotto per la prima volta in RedHat Enterprise versione 3 per risolvere i problemi di prestazioni con la Java Virtual Machine su Linux. Oggi, la libreria GNU C contiene implementazioni di entrambi i meccanismi di threading.

Nessuno di questi è un’implementazione di thread verdi, che una macchina virtuale gestirà ed eseguirà in modalità puramente utente. Quando usi la libreria pthread, il kernel crea un thread ogni volta che si avvia un programma.

È possibile trovare informazioni specifiche sui thread per qualsiasi processo in esecuzione nei file in /proc/<PID>/task . Questa è la posizione standard per le informazioni sui processi secondo lo standard Linux procfs. Per le applicazioni a thread singolo, sembrerà che in questa directory sia presente un record di attività con lo stesso valore del PID.

Logica di lavoro dei thread

I thread sono come i processi attualmente in esecuzione sul sistema operativo. Nei sistemi a processore singolo (ad esempio i microcontrollori), il kernel del sistema operativo simula i thread. Ciò consente l’esecuzione simultanea delle transazioni attraverso lo slicing.

Un sistema operativo single-core può eseguire solo un processo alla volta. Tuttavia, nei sistemi multi-core o multi-processore, questi processi possono essere eseguiti contemporaneamente.

Creazione filo in C

Puoi usare la funzione pthread_create per creare un nuovo thread. Il file di intestazione pthread.h include la sua definizione di firma insieme ad altre funzioni relative ai thread. I thread utilizzano lo stesso spazio degli indirizzi e descrittori di file del programma principale.

La libreria pthread include anche il supporto necessario per le operazioni mutex e condizionali richieste per le operazioni di sincronizzazione.

Quando utilizzi le funzioni della libreria pthread, devi assicurarti che il compilatore colleghi la libreria pthread nel tuo eseguibile. Se necessario, puoi istruire il compilatore a collegarsi alla libreria usando l’ opzione -l :

gcc -o test test_thread.c -lpthread

La funzione pthread_create ha la seguente firma:

int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg)

Restituisce 0 se la procedura va a buon fine. Se c’è un problema, restituisce un codice di errore diverso da zero. Nella firma della funzione sopra:

  • Il parametro thread è di tipo pthread_t . Il thread creato sarà sempre accessibile con questo riferimento.
  • Il parametro attr consente di specificare un comportamento personalizzato. È possibile utilizzare una serie di funzioni specifiche del thread che iniziano con pthread_attr_ per impostare questo valore. Le possibili personalizzazioni sono la politica di pianificazione, la dimensione dello stack e la politica di distacco.
  • start_routine specifica la funzione che verrà eseguita dal thread.
  • arg rappresenta una struttura dati generica passata alla funzione dal thread.

Ecco un’applicazione di esempio:

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

void *worker(void *data)
{
char *name = (char*)data;

for (int i = 0; i < 120; i++)
{
usleep(50000);
printf("Hi from thread name = %s\n", name);
}

printf("Thread %s done!\n", name);
return NULL;
}

int main(void)
{
pthread_t th1, th2;
pthread_create(&th1, NULL, worker, "X");
pthread_create(&th2, NULL, worker, "Y");
sleep(5);
printf("Exiting from main program\n");
return 0;
}

Output dal programma che mostra due thread in esecuzione contemporaneamente

Tipi di filo

Quando un thread ritorna dalla funzione main() in un’applicazione, tutti i thread terminano e il sistema libera tutte le risorse utilizzate dal programma. Allo stesso modo, quando esci da qualsiasi thread con un comando come exit() , il tuo programma terminerà tutti i thread.

Con la funzione pthread_join , invece, puoi aspettare che un thread termini. Il thread che utilizza questa funzione si bloccherà fino al termine del thread previsto. Le risorse che utilizzano dal sistema non vengono restituite nemmeno in casi come la terminazione di thread unibili, non pianificati dalla CPU o addirittura il mancato collegamento con ptread_join .

A volte ci sono situazioni in cui unirsi con pthread_join non ha senso; se è impossibile prevedere quando il thread finirà, per esempio. In questo caso, puoi assicurarti che il sistema restituisca automaticamente tutte le risorse nel punto in cui il thread ritorna.

Per raggiungere questo obiettivo, dovresti avviare i thread pertinenti con lo stato DETACHED . Quando si avvia un thread, lo stato DETACH può essere impostato tramite i valori di un attributo thread o con la funzione pthread_detach :

int pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
int pthread_detach(pthread_t thread);

Ecco un esempio di utilizzo di pthread_join(). Sostituisci la funzione main nel primo programma con la seguente:

int main(void)
{
pthread_t th1, th2;
pthread_create(&th1, NULL, worker, "X");
pthread_create(&th2, NULL, worker, "Y");
sleep(5);
printf("exiting from main program\n");
pthread_join(th1, NULL);
pthread_join(th2, NULL);
return 0;
}

Quando compili ed esegui il programma, il tuo output sarà:

Hi from thread Y
Hi from thread X
Hi from thread Y
...
Hi from thread Y
exiting from main program
Hi from thread X
...
Hi from thread X
Thread X done!
Hi from thread Y
Thread Y done!

Terminazione del filo

Puoi cancellare un thread con una chiamata a pthread_cancel, passando il corrispondente pthread_t id:

int pthread_cancel(pthread_t thread);

Puoi vederlo in azione nel codice seguente. Di nuovo, solo la funzione principale è diversa:

int main(void)
{
pthread_t th1, th2;
pthread_create(&th1, NULL, worker, "X");
pthread_create(&th2, NULL, worker, "Y");
sleep(1);
printf("====> Cancelling Thread Y!!\n");
pthread_cancel(th2);
usleep(100000);
printf("====> Cancelling Thread X!\n");
pthread_cancel(th1);
printf("exiting from main program\n");
return 0;
}

Output di un programma che mostra i thread in esecuzione e quindi annullati

Perché vengono creati i thread?

I sistemi operativi tentano sempre di eseguire thread su una o più CPU, da un elenco creato dall’utente o da un elenco di thread creato dall’utente. Alcuni thread non possono essere eseguiti perché sono in attesa di un segnale di input/output dall’hardware. Potrebbero anche essere in attesa volontariamente, in attesa di una risposta da un altro thread o avere un altro thread che li blocca.

Puoi regolare le risorse che assegni ai thread che crei usando pthread. Questa può essere una politica di pianificazione personalizzata oppure puoi scegliere algoritmi di pianificazione come FIFO o Round-robin se lo desideri.

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *