El multifil (multithreading) és una tècnica de programació que permet executar múltiples fils d'execució dins d'un mateix procés. Això pot millorar significativament el rendiment d'una aplicació, especialment en sistemes amb múltiples nuclis de CPU. En aquest tema, aprendrem com implementar multifil en C utilitzant la biblioteca POSIX Threads (pthreads).

Continguts

  1. Introducció al Multifil
  2. Creació de Fils
  3. Sincronització de Fils
  4. Comunicació entre Fils
  5. Exemples Pràctics
  6. Exercicis Pràctics

  1. Introducció al Multifil

El multifil permet que diferents parts d'un programa s'executin simultàniament. Cada fil és una seqüència d'instruccions que pot executar-se independentment de la resta. Els fils comparteixen el mateix espai d'adreces, cosa que facilita la comunicació entre ells però també requereix mecanismes de sincronització per evitar condicions de carrera.

Avantatges del Multifil

  • Millora del Rendiment: Permet aprofitar millor els recursos del sistema, especialment en sistemes multiprocés.
  • Responsivitat: Les aplicacions poden mantenir-se responsives mentre realitzen tasques de fons.
  • Modularitat: Facilita la divisió de tasques complexes en parts més petites i manejables.

  1. Creació de Fils

En C, la biblioteca POSIX Threads (pthreads) proporciona les funcions necessàries per treballar amb fils. A continuació, es mostra com crear i gestionar fils.

Incloure la Biblioteca Pthreads

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

Creació d'un Fil

Per crear un fil, utilitzem la funció pthread_create. Aquesta funció requereix quatre arguments:

  1. Un punter a pthread_t que identificarà el fil.
  2. Atributs del fil (pot ser NULL per defecte).
  3. La funció que el fil executarà.
  4. Els arguments per a la funció del fil (pot ser NULL si no hi ha arguments).

Exemple de Creació de Fils

void *print_message(void *message) {
    printf("%s\n", (char *)message);
    return NULL;
}

int main() {
    pthread_t thread1, thread2;
    const char *message1 = "Fil 1";
    const char *message2 = "Fil 2";

    // Creació dels fils
    pthread_create(&thread1, NULL, print_message, (void *)message1);
    pthread_create(&thread2, NULL, print_message, (void *)message2);

    // Esperar que els fils acabin
    pthread_join(thread1, NULL);
    pthread_join(thread2, NULL);

    return 0;
}

Explicació del Codi

  • pthread_create: Crea dos fils que executen la funció print_message.
  • pthread_join: Espera que els fils acabin abans de continuar.

  1. Sincronització de Fils

Quan múltiples fils accedeixen a dades compartides, és crucial sincronitzar-los per evitar condicions de carrera. La biblioteca pthreads proporciona diversos mecanismes de sincronització, com ara mutexos i variables de condició.

Mutexos

Un mutex (mutual exclusion) és un mecanisme que assegura que només un fil pugui accedir a una secció crítica del codi a la vegada.

Exemple d'Ús de Mutexos

pthread_mutex_t lock;

void *increment_counter(void *counter) {
    pthread_mutex_lock(&lock);
    (*(int *)counter)++;
    pthread_mutex_unlock(&lock);
    return NULL;
}

int main() {
    pthread_t threads[10];
    int counter = 0;

    pthread_mutex_init(&lock, NULL);

    for (int i = 0; i < 10; i++) {
        pthread_create(&threads[i], NULL, increment_counter, (void *)&counter);
    }

    for (int i = 0; i < 10; i++) {
        pthread_join(threads[i], NULL);
    }

    pthread_mutex_destroy(&lock);

    printf("Counter: %d\n", counter);
    return 0;
}

Explicació del Codi

  • pthread_mutex_lock: Bloqueja el mutex abans d'accedir a la secció crítica.
  • pthread_mutex_unlock: Allibera el mutex després d'accedir a la secció crítica.
  • pthread_mutex_init i pthread_mutex_destroy: Inicialitzen i destrueixen el mutex respectivament.

  1. Comunicació entre Fils

Els fils poden comunicar-se entre ells mitjançant variables compartides. No obstant això, és important utilitzar mecanismes de sincronització per evitar condicions de carrera.

Variables de Condició

Les variables de condició permeten que un fil esperi fins que una condició específica sigui certa.

Exemple d'Ús de Variables de Condició

pthread_mutex_t lock;
pthread_cond_t cond;
int ready = 0;

void *wait_for_signal(void *arg) {
    pthread_mutex_lock(&lock);
    while (!ready) {
        pthread_cond_wait(&cond, &lock);
    }
    printf("Senyal rebut!\n");
    pthread_mutex_unlock(&lock);
    return NULL;
}

void *send_signal(void *arg) {
    pthread_mutex_lock(&lock);
    ready = 1;
    pthread_cond_signal(&cond);
    pthread_mutex_unlock(&lock);
    return NULL;
}

int main() {
    pthread_t thread1, thread2;

    pthread_mutex_init(&lock, NULL);
    pthread_cond_init(&cond, NULL);

    pthread_create(&thread1, NULL, wait_for_signal, NULL);
    pthread_create(&thread2, NULL, send_signal, NULL);

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

    pthread_mutex_destroy(&lock);
    pthread_cond_destroy(&cond);

    return 0;
}

Explicació del Codi

  • pthread_cond_wait: Espera fins que la condició sigui certa.
  • pthread_cond_signal: Senyala que la condició és certa.
  • pthread_cond_init i pthread_cond_destroy: Inicialitzen i destrueixen la variable de condició respectivament.

  1. Exemples Pràctics

Exemple 1: Càlcul Paral·lel

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

#define NUM_THREADS 4

void *compute_sum(void *arg) {
    int *arr = (int *)arg;
    int sum = 0;
    for (int i = 0; i < 1000; i++) {
        sum += arr[i];
    }
    printf("Sum: %d\n", sum);
    return NULL;
}

int main() {
    pthread_t threads[NUM_THREADS];
    int data[NUM_THREADS][1000];

    // Inicialitzar dades
    for (int i = 0; i < NUM_THREADS; i++) {
        for (int j = 0; j < 1000; j++) {
            data[i][j] = rand() % 100;
        }
    }

    // Crear fils
    for (int i = 0; i < NUM_THREADS; i++) {
        pthread_create(&threads[i], NULL, compute_sum, (void *)data[i]);
    }

    // Esperar que els fils acabin
    for (int i = 0; i < NUM_THREADS; i++) {
        pthread_join(threads[i], NULL);
    }

    return 0;
}

Explicació del Codi

  • compute_sum: Calcula la suma d'un array de 1000 elements.
  • pthread_create: Crea quatre fils per calcular la suma de quatre arrays diferents.

  1. Exercicis Pràctics

Exercici 1: Càlcul de Factorial amb Fils

Escriu un programa que calculi el factorial d'un nombre utilitzant múltiples fils. Cada fil ha de calcular una part del producte.

Exercici 2: Cerca en un Array

Escriu un programa que cerqui un valor en un array gran utilitzant múltiples fils. Cada fil ha de cercar en una part diferent de l'array.

Solucions

Solució Exercici 1

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

#define NUM_THREADS 4

typedef struct {
    int start;
    int end;
    long long result;
} ThreadData;

void *compute_factorial(void *arg) {
    ThreadData *data = (ThreadData *)arg;
    data->result = 1;
    for (int i = data->start; i <= data->end; i++) {
        data->result *= i;
    }
    return NULL;
}

int main() {
    pthread_t threads[NUM_THREADS];
    ThreadData thread_data[NUM_THREADS];
    int n = 20;
    int range = n / NUM_THREADS;
    long long factorial = 1;

    for (int i = 0; i < NUM_THREADS; i++) {
        thread_data[i].start = i * range + 1;
        thread_data[i].end = (i == NUM_THREADS - 1) ? n : (i + 1) * range;
        pthread_create(&threads[i], NULL, compute_factorial, (void *)&thread_data[i]);
    }

    for (int i = 0; i < NUM_THREADS; i++) {
        pthread_join(threads[i], NULL);
        factorial *= thread_data[i].result;
    }

    printf("Factorial de %d és %lld\n", n, factorial);
    return 0;
}

Solució Exercici 2

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

#define NUM_THREADS 4
#define ARRAY_SIZE 10000

typedef struct {
    int *array;
    int start;
    int end;
    int value;
    int found;
} ThreadData;

void *search_array(void *arg) {
    ThreadData *data = (ThreadData *)arg;
    data->found = 0;
    for (int i = data->start; i < data->end; i++) {
        if (data->array[i] == data->value) {
            data->found = 1;
            break;
        }
    }
    return NULL;
}

int main() {
    pthread_t threads[NUM_THREADS];
    ThreadData thread_data[NUM_THREADS];
    int array[ARRAY_SIZE];
    int value = 5000;
    int found = 0;

    // Inicialitzar array
    for (int i = 0; i < ARRAY_SIZE; i++) {
        array[i] = rand() % 10000;
    }

    int range = ARRAY_SIZE / NUM_THREADS;

    for (int i = 0; i < NUM_THREADS; i++) {
        thread_data[i].array = array;
        thread_data[i].start = i * range;
        thread_data[i].end = (i == NUM_THREADS - 1) ? ARRAY_SIZE : (i + 1) * range;
        thread_data[i].value = value;
        pthread_create(&threads[i], NULL, search_array, (void *)&thread_data[i]);
    }

    for (int i = 0; i < NUM_THREADS; i++) {
        pthread_join(threads[i], NULL);
        if (thread_data[i].found) {
            found = 1;
        }
    }

    if (found) {
        printf("Valor %d trobat en l'array.\n", value);
    } else {
        printf("Valor %d no trobat en l'array.\n", value);
    }

    return 0;
}

Conclusió

En aquest tema, hem après els conceptes bàsics del multifil en C utilitzant la biblioteca pthreads. Hem vist com crear i gestionar fils, com sincronitzar-los utilitzant mutexos i variables de condició, i com comunicar-se entre ells. També hem treballat amb exemples pràctics i exercicis per reforçar els conceptes apresos. El multifil és una eina poderosa que pot millorar significativament el rendiment de les aplicacions, però també requereix una gestió acurada per evitar problemes de sincronització i condicions de carrera.

Curs de Programació en C

Mòdul 1: Introducció al C

Mòdul 2: Tipus de Dades i Variables

Mòdul 3: Flux de Control

Mòdul 4: Funcions

Mòdul 5: Arrays i Strings

Mòdul 6: Punteres

Mòdul 7: Estructures i Unions

Mòdul 8: Assignació Dinàmica de Memòria

Mòdul 9: Gestió d'Arxius

Mòdul 10: Temes Avançats

Mòdul 11: Millors Pràctiques i Optimització

Mòdul 12: Projecte i Avaluació Final

© Copyright 2024. Tots els drets reservats