El multihilo es una técnica de programación que permite la ejecución concurrente de múltiples hilos de ejecución 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 módulo, aprenderemos los conceptos básicos del multihilo en C++, cómo crear y gestionar hilos, y cómo sincronizar hilos para evitar condiciones de carrera y otros problemas de concurrencia.

Contenidos

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, lo que puede llevar a una mejor utilización de los recursos del sistema y a una mayor eficiencia.

Conceptos Clave

  • Hilo (Thread): La unidad más pequeña de procesamiento que puede ser programada por un sistema operativo.
  • Concurrencia: La capacidad de un sistema para manejar múltiples tareas al mismo tiempo.
  • Paralelismo: La ejecución simultánea de múltiples tareas en diferentes núcleos de CPU.

Creación de Hilos

En C++, la biblioteca estándar proporciona la clase std::thread 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 <iostream>
#include <thread>

// Función que será ejecutada por el hilo
void printMessage() {
    std::cout << "Hola desde el hilo!" << std::endl;
}

int main() {
    // Crear un hilo que ejecuta la función printMessage
    std::thread t(printMessage);

    // Esperar a que el hilo termine
    t.join();

    std::cout << "Hola desde el hilo principal!" << std::endl;
    return 0;
}

Explicación

  • std::thread t(printMessage);: Crea un nuevo hilo que ejecuta la función printMessage.
  • t.join();: Espera a que el hilo t termine antes de continuar con la ejecución del hilo principal.

Sincronización de Hilos

La sincronización es crucial en la programación multihilo para evitar problemas como condiciones de carrera. La biblioteca estándar de C++ proporciona varias herramientas para la sincronización, como std::mutex, std::lock_guard, y std::unique_lock.

Mutex y Bloqueo

Un mutex (mutual exclusion) es un objeto que puede ser bloqueado por un hilo para asegurar que solo un hilo pueda acceder a una sección crítica del código a la vez.

Ejemplo con Mutex

#include <iostream>
#include <thread>
#include <mutex>

std::mutex mtx;

void printMessage(const std::string& message) {
    std::lock_guard<std::mutex> lock(mtx);
    std::cout << message << std::endl;
}

int main() {
    std::thread t1(printMessage, "Hola desde el hilo 1!");
    std::thread t2(printMessage, "Hola desde el hilo 2!");

    t1.join();
    t2.join();

    return 0;
}

Explicación

  • std::mutex mtx;: Declara un objeto mutex.
  • std::lock_guardstd::mutex lock(mtx);: Bloquea el mutex al inicio del bloque y lo desbloquea automáticamente al final del bloque.

Condiciones de Carrera

Las condiciones de carrera ocurren cuando dos o más hilos acceden a una variable compartida al mismo tiempo y al menos uno de los accesos es una escritura. Esto puede llevar a resultados impredecibles y errores difíciles de depurar.

Ejemplo de Condición de Carrera

#include <iostream>
#include <thread>

int counter = 0;

void incrementCounter() {
    for (int i = 0; i < 1000; ++i) {
        ++counter;
    }
}

int main() {
    std::thread t1(incrementCounter);
    std::thread t2(incrementCounter);

    t1.join();
    t2.join();

    std::cout << "Counter: " << counter << std::endl;
    return 0;
}

Solución con Mutex

#include <iostream>
#include <thread>
#include <mutex>

int counter = 0;
std::mutex mtx;

void incrementCounter() {
    for (int i = 0; i < 1000; ++i) {
        std::lock_guard<std::mutex> lock(mtx);
        ++counter;
    }
}

int main() {
    std::thread t1(incrementCounter);
    std::thread t2(incrementCounter);

    t1.join();
    t2.join();

    std::cout << "Counter: " << counter << std::endl;
    return 0;
}

Ejercicios Prácticos

Ejercicio 1: Crear y Ejecutar Hilos

Crea un programa que inicie tres hilos, cada uno de los cuales imprime un mensaje diferente.

Ejercicio 2: Sincronización con Mutex

Modifica el programa del Ejercicio 1 para que los mensajes se impriman en orden utilizando std::mutex.

Ejercicio 3: Condiciones de Carrera

Escribe un programa que demuestre una condición de carrera y luego soluciona el problema utilizando std::mutex.

Soluciones

Ejercicio 1: Crear y Ejecutar Hilos

#include <iostream>
#include <thread>

void printMessage1() {
    std::cout << "Mensaje desde el hilo 1" << std::endl;
}

void printMessage2() {
    std::cout << "Mensaje desde el hilo 2" << std::endl;
}

void printMessage3() {
    std::cout << "Mensaje desde el hilo 3" << std::endl;
}

int main() {
    std::thread t1(printMessage1);
    std::thread t2(printMessage2);
    std::thread t3(printMessage3);

    t1.join();
    t2.join();
    t3.join();

    return 0;
}

Ejercicio 2: Sincronización con Mutex

#include <iostream>
#include <thread>
#include <mutex>

std::mutex mtx;

void printMessage(const std::string& message) {
    std::lock_guard<std::mutex> lock(mtx);
    std::cout << message << std::endl;
}

int main() {
    std::thread t1(printMessage, "Mensaje desde el hilo 1");
    std::thread t2(printMessage, "Mensaje desde el hilo 2");
    std::thread t3(printMessage, "Mensaje desde el hilo 3");

    t1.join();
    t2.join();
    t3.join();

    return 0;
}

Ejercicio 3: Condiciones de Carrera

#include <iostream>
#include <thread>
#include <mutex>

int counter = 0;
std::mutex mtx;

void incrementCounter() {
    for (int i = 0; i < 1000; ++i) {
        std::lock_guard<std::mutex> lock(mtx);
        ++counter;
    }
}

int main() {
    std::thread t1(incrementCounter);
    std::thread t2(incrementCounter);

    t1.join();
    t2.join();

    std::cout << "Counter: " << counter << std::endl;
    return 0;
}

Conclusión

En este módulo, hemos aprendido los conceptos básicos del multihilo en C++, cómo crear y gestionar hilos, y cómo sincronizarlos para evitar problemas de concurrencia. El multihilo es una herramienta poderosa que puede mejorar significativamente el rendimiento de las aplicaciones, pero también requiere una gestión cuidadosa para evitar errores difíciles de depurar. Con la práctica y la comprensión de estos conceptos, estarás bien preparado para escribir programas concurrentes eficientes y seguros.

© Copyright 2024. Todos los derechos reservados