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.
Curso de Programación en C++
Módulo 1: Introducción a C++
- Introducción a C++
- Configuración del Entorno de Desarrollo
- Sintaxis y Estructura Básica
- Variables y Tipos de Datos
- Entrada y Salida
Módulo 2: Estructuras de Control
Módulo 3: Funciones
- Introducción a las Funciones
- Parámetros de Función y Tipos de Retorno
- Sobrecarga de Funciones
- Recursión
Módulo 4: Arreglos y Cadenas
- Introducción a los Arreglos
- Arreglos Multidimensionales
- Introducción a las Cadenas
- Manipulación de Cadenas
Módulo 5: Punteros y Referencias
- Introducción a los Punteros
- Aritmética de Punteros
- Punteros y Arreglos
- Referencias
- Asignación Dinámica de Memoria
Módulo 6: Programación Orientada a Objetos
- Introducción a la POO
- Clases y Objetos
- Constructores y Destructores
- Herencia
- Polimorfismo
- Encapsulación y Abstracción
Módulo 7: Temas Avanzados
- Plantillas
- Manejo de Excepciones
- Entrada/Salida de Archivos
- Biblioteca Estándar de Plantillas (STL)
- Expresiones Lambda
- Multihilo