🚀 Nuova versione beta disponibile! Feedback o problemi? Contattaci

Introduzione ai Thread in C

Codegrind TeamAug 23 2024

La programmazione multithread in C permette di eseguire più operazioni simultaneamente all’interno di un programma, migliorando le prestazioni e sfruttando meglio le risorse hardware, specialmente su sistemi con CPU multi-core. Questa guida fornisce un’introduzione ai thread in C, spiegando come crearli, sincronizzarli e gestirli, utilizzando la libreria POSIX threads (pthreads), che è lo standard per la programmazione multithread su sistemi Unix-like.

Cos’è un Thread?

Un thread è la più piccola unità di elaborazione che può essere eseguita da un sistema operativo. In un’applicazione multithread, più thread vengono eseguiti contemporaneamente, condividendo lo stesso spazio di indirizzamento e le stesse risorse globali del programma. Questo permette di eseguire operazioni parallele, come il caricamento di file mentre si aggiorna l’interfaccia utente.

Vantaggi della Programmazione Multithread

  • Prestazioni Migliorate: I thread permettono di eseguire operazioni parallele, riducendo i tempi di attesa e sfruttando meglio i processori multi-core.
  • Risposta Migliorata: Le applicazioni multithread possono rispondere più rapidamente agli input dell’utente o ad eventi esterni.
  • Modularità: Le operazioni complesse possono essere suddivise in thread separati, rendendo il codice più modulare e gestibile.

Creazione di Thread in C

Per creare thread in C, si utilizza la libreria pthread, che fornisce una serie di funzioni per la gestione dei thread.

1. Includere la Libreria pthread

Prima di poter utilizzare i thread, è necessario includere l’header pthread:

#include <pthread.h>

2. Creazione di un Thread

La funzione pthread_create viene utilizzata per creare un nuovo thread. Richiede quattro argomenti:

  • Un puntatore a una variabile di tipo pthread_t che conterrà l’identificatore del thread.
  • Un puntatore a un oggetto pthread_attr_t che specifica gli attributi del thread (può essere NULL per usare gli attributi predefiniti).
  • La funzione che il thread eseguirà.
  • Un argomento passato alla funzione del thread (può essere NULL se non necessario).

Esempio di Creazione di un Thread

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

void *stampa_messaggio(void *arg) {
    printf("Ciao dal thread!\n");
    return NULL;
}

int main() {
    pthread_t mio_thread;

    if (pthread_create(&mio_thread, NULL, stampa_messaggio, NULL) != 0) {
        printf("Errore nella creazione del thread\n");
        return 1;
    }

    pthread_join(mio_thread, NULL);  // Attende che il thread termini
    return 0;
}

In questo esempio, il programma crea un thread che esegue la funzione stampa_messaggio. La funzione pthread_join viene utilizzata per aspettare che il thread termini prima di continuare l’esecuzione del programma principale.

Sincronizzazione dei Thread

Quando più thread condividono risorse, è necessario sincronizzarli per evitare race conditions e garantire che i dati siano accessibili in modo sicuro. La libreria pthread offre diversi meccanismi di sincronizzazione.

1. Mutex

Un mutex (mutual exclusion) è un meccanismo di sincronizzazione che garantisce che solo un thread alla volta possa accedere a una risorsa condivisa.

Esempio di Uso di un Mutex

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

pthread_mutex_t mio_mutex;
int contatore = 0;

void *incrementa_contatore(void *arg) {
    pthread_mutex_lock(&mio_mutex);
    contatore++;
    printf("Contatore: %d\n", contatore);
    pthread_mutex_unlock(&mio_mutex);
    return NULL;
}

int main() {
    pthread_t thread1, thread2;
    pthread_mutex_init(&mio_mutex, NULL);

    pthread_create(&thread1, NULL, incrementa_contatore, NULL);
    pthread_create(&thread2, NULL, incrementa_contatore, NULL);

    pthread_join(thread1, NULL);
    pthread_join(thread2, NULL);

    pthread_mutex_destroy(&mio_mutex);
    return 0;
}

In questo esempio, il mutex mio_mutex garantisce che solo un thread alla volta possa incrementare e stampare il valore di contatore, evitando una race condition.

2. Condizioni

Le variabili di condizione vengono utilizzate per sospendere un thread fino a quando una certa condizione non è soddisfatta.

Esempio di Uso di una Condizione

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

pthread_mutex_t mio_mutex;
pthread_cond_t condizione;
int pronto = 0;

void *attendi_condizione(void *arg) {
    pthread_mutex_lock(&mio_mutex);
    while (!pronto) {
        pthread_cond_wait(&condizione, &mio_mutex);
    }
    printf("Condizione soddisfatta!\n");
    pthread_mutex_unlock(&mio_mutex);
    return NULL;
}

void *imposta_condizione(void *arg) {
    pthread_mutex_lock(&mio_mutex);
    pronto = 1;
    pthread_cond_signal(&condizione);
    pthread_mutex_unlock(&mio_mutex);
    return NULL;
}

int main() {
    pthread_t thread1, thread2;
    pthread_mutex_init(&mio_mutex, NULL);
    pthread_cond_init(&condizione, NULL);

    pthread_create(&thread1, NULL, attendi_condizione, NULL);
    pthread_create(&thread2, NULL, imposta_condizione, NULL);

    pthread_join(thread1, NULL);
    pthread_join(thread2, NULL);

    pthread_mutex_destroy(&mio_mutex);
    pthread_cond_destroy(&condizione);
    return 0;
}

In questo esempio, attendi_condizione aspetta che la variabile pronto diventi 1, mentre imposta_condizione imposta questa variabile e segnala la condizione, permettendo all’altro thread di continuare.

Gestione dei Thread

1. Terminazione di un Thread

Un thread termina automaticamente quando la funzione associata ritorna. Tuttavia, puoi anche terminare un thread in anticipo usando pthread_exit.

Esempio di Terminazione di un Thread

void *esempio_thread(void *arg) {
    printf("Esecuzione del thread...\n");
    pthread_exit(NULL);
}

2. Unione dei Thread

La funzione pthread_join è utilizzata per aspettare la terminazione di un thread e recuperare il suo valore di ritorno.

3. Attributi dei Thread

È possibile configurare attributi specifici per i thread, come la loro dimensione dello stack o il comportamento di detach, usando la struttura pthread_attr_t.

Esempio di Uso degli Attributi dei Thread

pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
pthread_create(&mio_thread, &attr, funzione_thread, NULL);
pthread_attr_destroy(&attr);

In questo esempio, il thread viene creato in uno stato “detached”, il che significa che non è necessario utilizzare pthread_join per aspettare la sua terminazione.

Conclusioni

La programmazione multithread in C offre potenti strumenti per migliorare le prestazioni delle applicazioni sfruttando il parallelismo. Comprendere come creare, sincronizzare e gestire thread è fondamentale per scrivere software efficiente e sicuro. Sebbene i thread possano introdurre complessità, specialmente quando si tratta di sincronizzazione e gestione delle risorse condivise, con una buona padronanza delle tecniche e degli strumenti disponibili, puoi sviluppare applicazioni che traggono pieno vantaggio dalle capacità moderne dei processori multi-core.