Introducción: ¿por qué se necesitan colas de tareas en C++?
\n\nEn la programación moderna, especialmente al trabajar con aplicaciones multiproceso, a menudo surge la necesidad de organizar la ejecución de tareas en el orden en que llegan. Las colas de tareas (task queues) son un patrón fundamental que permite distribuir eficientemente el trabajo entre hilos, procesar solicitudes de servidores web, gestionar procesos en segundo plano y evitar condiciones de carrera.
\n\nEn C++, la biblioteca estándar proporciona el potente contenedor std::queue, que es ideal para implementar colas de tareas. Sin embargo, en la práctica no solo se necesita almacenar elementos, sino transferirlos de forma segura entre hilos, manejar errores y gestionar el ciclo de vida de las tareas. En este artículo, analizaremos cómo crear colas de tareas en C++ desde cero, utilizando las capacidades modernas del lenguaje (C++11 y posteriores).
Abordaremos:
\n- \n
- Uso básico de
std::queue\n - Cola de tareas segura para hilos \n
- Implementación de un grupo de hilos (thread pool) \n
- Colas prioritarias \n
1. Cola de tareas básica con std::queue
\n\nComencemos con un ejemplo simple. std::queue es un adaptador de contenedor que funciona bajo el principio FIFO (First In, First Out). Proporciona los métodos push(), pop(), front() y empty().
Así es como se puede organizar una cola de tareas, donde cada tarea es una función (o un objeto invocable):
\n\n#include <iostream>\n#include <queue>\n#include <functional>\n\nint main() {\n // Cola de tareas, donde cada tarea es std::function<void()>\n std::queue<std::function<void()>> taskQueue;\n\n // Agregamos tareas\n taskQueue.push([]() { std::cout << "Tarea 1 completada\\n"; });\n taskQueue.push([]() { std::cout << "Tarea 2 completada\\n"; });\n taskQueue.push([]() { std::cout << "Tarea 3 completada\\n"; });\n\n // Procesamos la cola\n while (!taskQueue.empty()) {\n auto task = taskQueue.front();\n task(); // Ejecutamos la tarea\n taskQueue.pop(); // Eliminamos la tarea completada\n }\n\n return 0;\n}\n\nEste código funciona, pero solo en modo de un solo hilo. Si varios hilos agregan o extraen tareas simultáneamente, se producirá una condición de carrera. Para el multiproceso se necesita sincronización.
\n\n2. Cola de tareas segura para hilos
\n\nPara hacer que la cola sea segura para el acceso multiproceso, utilizamos un mutex (std::mutex) y una variable de condición (std::condition_variable). Este es el patrón clásico productor-consumidor.
Implementación de una cola segura para hilos:
\n\n#include <queue>\n#include <mutex>\n#include <condition_variable>\n#include <functional>\n#include <thread>\n\nclass ThreadSafeQueue {\nprivate:\n std::queue<std::function<void()>> queue_;\n std::mutex mutex_;\n std::condition_variable cond_;\n bool stop_ = false;\n\npublic:\n void push(std::function<void()> task) {\n std::lock_guard<std::mutex> lock(mutex_);\n queue_.push(task);\n cond_.notify_one(); // Notificamos a un hilo en espera\n }\n\n std::function<void()> pop() {\n std::unique_lock<std::mutex> lock(mutex_);\n cond_.wait(lock, [this]() { return !queue_.empty() || stop_; });\n if (stop_ && queue_.empty()) {\n return nullptr; // Señal de parada\n }\n auto task = queue_.front();\n queue_.pop();\n return task;\n }\n\n void stop() {\n std::lock_guard<std::mutex> lock(mutex_);\n stop_ = true;\n cond_.notify_all(); // Despertamos todos los hilos para finalizar\n }\n};\n\nPuntos clave:
\n- \n
std::lock_gu