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
- Introducció al Multifil
- Creació de Fils
- Sincronització de Fils
- Comunicació entre Fils
- Exemples Pràctics
- Exercicis Pràctics
- 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.
- 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
Creació d'un Fil
Per crear un fil, utilitzem la funció pthread_create
. Aquesta funció requereix quatre arguments:
- Un punter a
pthread_t
que identificarà el fil. - Atributs del fil (pot ser
NULL
per defecte). - La funció que el fil executarà.
- 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.
- 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
ipthread_mutex_destroy
: Inicialitzen i destrueixen el mutex respectivament.
- 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
ipthread_cond_destroy
: Inicialitzen i destrueixen la variable de condició respectivament.
- 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.
- 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
- Introducció a la Programació
- Configuració de l'Entorn de Desenvolupament
- Programa Hello World
- Sintaxi i Estructura Bàsiques
Mòdul 2: Tipus de Dades i Variables
Mòdul 3: Flux de Control
Mòdul 4: Funcions
- Introducció a les Funcions
- Arguments de Funció i Valors de Retorn
- Àmbit i Durada de les Variables
- Funcions Recursives
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
- Introducció a la Gestió d'Arxius
- Lectura i Escriptura d'Arxius
- Posicionament d'Arxius
- Gestió d'Errors en Operacions d'Arxius
Mòdul 10: Temes Avançats
- Directives del Preprocessador
- Arguments de Línia de Comandes
- Llistes d'Arguments Variables
- Multifil en C
Mòdul 11: Millors Pràctiques i Optimització
- Llegibilitat del Codi i Documentació
- Tècniques de Depuració
- Optimització del Rendiment
- Consideracions de Seguretat