El multihilo es una técnica de programación que permite la ejecución concurrente de múltiples hilos dentro de un solo proceso. Esto puede mejorar significativamente el rendimiento de las aplicaciones, especialmente en sistemas con múltiples núcleos de CPU. En este tema, aprenderemos cómo implementar multihilo en C utilizando la biblioteca POSIX Threads (pthreads).

Contenido

  1. Introducción al Multihilo
  2. Creación de Hilos
  3. Sincronización de Hilos
  4. Ejemplos Prácticos
  5. Ejercicios

  1. Introducción al Multihilo

El multihilo permite que un programa realice múltiples tareas simultáneamente. Cada hilo puede ejecutar una parte diferente del código del programa, lo que puede llevar a una mejor utilización de los recursos del sistema y a una mayor eficiencia.

Ventajas del Multihilo

  • Mejor rendimiento: Permite la ejecución concurrente de tareas.
  • Mejor utilización de recursos: Aprovecha mejor los recursos del sistema, especialmente en sistemas con múltiples núcleos.
  • Mayor capacidad de respuesta: Mejora la capacidad de respuesta de las aplicaciones, especialmente en aplicaciones de interfaz de usuario.

Desventajas del Multihilo

  • Complejidad: La programación multihilo puede ser compleja y propensa a errores.
  • Problemas de sincronización: Los hilos pueden interferir entre sí si no se gestionan adecuadamente.

  1. Creación de Hilos

En C, la biblioteca POSIX Threads (pthreads) proporciona funciones para crear y gestionar hilos. A continuación, se muestra un ejemplo básico de cómo crear y ejecutar un hilo.

Ejemplo Básico

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

// Función que será ejecutada por el hilo
void* print_message(void* ptr) {
    char* message = (char*) ptr;
    printf("%s\n", message);
    return NULL;
}

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

    // Crear hilos
    pthread_create(&thread1, NULL, print_message, (void*) message1);
    pthread_create(&thread2, NULL, print_message, (void*) message2);

    // Esperar a que los hilos terminen
    pthread_join(thread1, NULL);
    pthread_join(thread2, NULL);

    return 0;
}

Explicación del Código

  1. Inclusión de la biblioteca pthread: #include <pthread.h>
  2. Definición de la función del hilo: void* print_message(void* ptr)
  3. Creación de hilos: pthread_create(&thread1, NULL, print_message, (void*) message1)
  4. Esperar a que los hilos terminen: pthread_join(thread1, NULL)

  1. Sincronización de Hilos

La sincronización es crucial en la programación multihilo para evitar condiciones de carrera y asegurar que los hilos no interfieran entre sí. Las herramientas más comunes para la sincronización son los mutex y las variables de condición.

Uso de Mutex

Un mutex (mutual exclusion) es una primitiva de sincronización que permite que solo un hilo acceda a una sección crítica del código a la vez.

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

pthread_mutex_t lock;

void* print_message(void* ptr) {
    char* message = (char*) ptr;

    pthread_mutex_lock(&lock);
    printf("%s\n", message);
    pthread_mutex_unlock(&lock);

    return NULL;
}

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

    // Inicializar el mutex
    pthread_mutex_init(&lock, NULL);

    // Crear hilos
    pthread_create(&thread1, NULL, print_message, (void*) message1);
    pthread_create(&thread2, NULL, print_message, (void*) message2);

    // Esperar a que los hilos terminen
    pthread_join(thread1, NULL);
    pthread_join(thread2, NULL);

    // Destruir el mutex
    pthread_mutex_destroy(&lock);

    return 0;
}

Explicación del Código

  1. Inicialización del mutex: pthread_mutex_init(&lock, NULL)
  2. Bloqueo del mutex: pthread_mutex_lock(&lock)
  3. Desbloqueo del mutex: pthread_mutex_unlock(&lock)
  4. Destrucción del mutex: pthread_mutex_destroy(&lock)

  1. Ejemplos Prácticos

Ejemplo 1: Contador Compartido

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

#define NUM_THREADS 5

pthread_mutex_t lock;
int counter = 0;

void* increment_counter(void* ptr) {
    pthread_mutex_lock(&lock);
    counter++;
    printf("Contador: %d\n", counter);
    pthread_mutex_unlock(&lock);
    return NULL;
}

int main() {
    pthread_t threads[NUM_THREADS];

    // Inicializar el mutex
    pthread_mutex_init(&lock, NULL);

    // Crear hilos
    for (int i = 0; i < NUM_THREADS; i++) {
        pthread_create(&threads[i], NULL, increment_counter, NULL);
    }

    // Esperar a que los hilos terminen
    for (int i = 0; i < NUM_THREADS; i++) {
        pthread_join(threads[i], NULL);
    }

    // Destruir el mutex
    pthread_mutex_destroy(&lock);

    return 0;
}

Ejemplo 2: Productor-Consumidor

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

#define BUFFER_SIZE 10

int buffer[BUFFER_SIZE];
int count = 0;
pthread_mutex_t lock;
pthread_cond_t cond;

void* producer(void* ptr) {
    for (int i = 0; i < 20; i++) {
        pthread_mutex_lock(&lock);
        while (count == BUFFER_SIZE) {
            pthread_cond_wait(&cond, &lock);
        }
        buffer[count++] = i;
        printf("Producido: %d\n", i);
        pthread_cond_signal(&cond);
        pthread_mutex_unlock(&lock);
    }
    return NULL;
}

void* consumer(void* ptr) {
    for (int i = 0; i < 20; i++) {
        pthread_mutex_lock(&lock);
        while (count == 0) {
            pthread_cond_wait(&cond, &lock);
        }
        int item = buffer[--count];
        printf("Consumido: %d\n", item);
        pthread_cond_signal(&cond);
        pthread_mutex_unlock(&lock);
    }
    return NULL;
}

int main() {
    pthread_t prod, cons;

    // Inicializar el mutex y la variable de condición
    pthread_mutex_init(&lock, NULL);
    pthread_cond_init(&cond, NULL);

    // Crear hilos
    pthread_create(&prod, NULL, producer, NULL);
    pthread_create(&cons, NULL, consumer, NULL);

    // Esperar a que los hilos terminen
    pthread_join(prod, NULL);
    pthread_join(cons, NULL);

    // Destruir el mutex y la variable de condición
    pthread_mutex_destroy(&lock);
    pthread_cond_destroy(&cond);

    return 0;
}

  1. Ejercicios

Ejercicio 1: Hilos Concurrentes

Descripción: Crea un programa que inicie tres hilos, cada uno de los cuales imprime un mensaje diferente. Asegúrate de que los mensajes se impriman en orden utilizando mutex.

Solución:

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

pthread_mutex_t lock;

void* print_message(void* ptr) {
    char* message = (char*) ptr;

    pthread_mutex_lock(&lock);
    printf("%s\n", message);
    pthread_mutex_unlock(&lock);

    return NULL;
}

int main() {
    pthread_t thread1, thread2, thread3;
    const char* message1 = "Mensaje 1";
    const char* message2 = "Mensaje 2";
    const char* message3 = "Mensaje 3";

    // Inicializar el mutex
    pthread_mutex_init(&lock, NULL);

    // Crear hilos
    pthread_create(&thread1, NULL, print_message, (void*) message1);
    pthread_create(&thread2, NULL, print_message, (void*) message2);
    pthread_create(&thread3, NULL, print_message, (void*) message3);

    // Esperar a que los hilos terminen
    pthread_join(thread1, NULL);
    pthread_join(thread2, NULL);
    pthread_join(thread3, NULL);

    // Destruir el mutex
    pthread_mutex_destroy(&lock);

    return 0;
}

Ejercicio 2: Productor-Consumidor con Múltiples Productores y Consumidores

Descripción: Modifica el ejemplo del productor-consumidor para que haya dos productores y dos consumidores.

Solución:

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

#define BUFFER_SIZE 10

int buffer[BUFFER_SIZE];
int count = 0;
pthread_mutex_t lock;
pthread_cond_t cond;

void* producer(void* ptr) {
    for (int i = 0; i < 20; i++) {
        pthread_mutex_lock(&lock);
        while (count == BUFFER_SIZE) {
            pthread_cond_wait(&cond, &lock);
        }
        buffer[count++] = i;
        printf("Producido: %d\n", i);
        pthread_cond_signal(&cond);
        pthread_mutex_unlock(&lock);
    }
    return NULL;
}

void* consumer(void* ptr) {
    for (int i = 0; i < 20; i++) {
        pthread_mutex_lock(&lock);
        while (count == 0) {
            pthread_cond_wait(&cond, &lock);
        }
        int item = buffer[--count];
        printf("Consumido: %d\n", item);
        pthread_cond_signal(&cond);
        pthread_mutex_unlock(&lock);
    }
    return NULL;
}

int main() {
    pthread_t prod1, prod2, cons1, cons2;

    // Inicializar el mutex y la variable de condición
    pthread_mutex_init(&lock, NULL);
    pthread_cond_init(&cond, NULL);

    // Crear hilos
    pthread_create(&prod1, NULL, producer, NULL);
    pthread_create(&prod2, NULL, producer, NULL);
    pthread_create(&cons1, NULL, consumer, NULL);
    pthread_create(&cons2, NULL, consumer, NULL);

    // Esperar a que los hilos terminen
    pthread_join(prod1, NULL);
    pthread_join(prod2, NULL);
    pthread_join(cons1, NULL);
    pthread_join(cons2, NULL);

    // Destruir el mutex y la variable de condición
    pthread_mutex_destroy(&lock);
    pthread_cond_destroy(&cond);

    return 0;
}

Conclusión

En esta sección, hemos aprendido los conceptos básicos del multihilo en C utilizando la biblioteca POSIX Threads (pthreads). Hemos cubierto la creación de hilos, la sincronización utilizando mutex y variables de condición, y hemos visto ejemplos prácticos y ejercicios para reforzar los conceptos. El multihilo es una herramienta poderosa que, cuando se usa correctamente, puede mejorar significativamente el rendimiento y la eficiencia de las aplicaciones.

Curso de Programación en C

Módulo 1: Introducción a C

Módulo 2: Tipos de Datos y Variables

Módulo 3: Flujo de Control

Módulo 4: Funciones

Módulo 5: Arreglos y Cadenas

Módulo 6: Punteros

Módulo 7: Estructuras y Uniones

Módulo 8: Asignación Dinámica de Memoria

Módulo 9: Manejo de Archivos

Módulo 10: Temas Avanzados

Módulo 11: Mejores Prácticas y Optimización

Módulo 12: Proyecto y Evaluación Final

© Copyright 2024. Todos los derechos reservados