Colas de tareas en C++: implementación, aplicación y optimización

Entrenador de Python en Línea para Principiantes

Aprende Python fácilmente sin sobrecargarte de teoría. Resuelve tareas prácticas con verificación automática, recibe pistas y escribe código directamente en el navegador, sin instalar nada.

Start Course

Trabajo con colas de tareas en C++: de lo simple a lo complejo



En el desarrollo moderno con C++, rara vez se prescinde del procesamiento asíncrono de datos. Las colas de tareas (task queues) son una de las herramientas clave para organizar la ejecución paralela de código. Permiten distribuir el trabajo entre hilos, evitar bloqueos y utilizar eficientemente los recursos del procesador. En este artículo, analizaremos cómo implementar una cola de tareas en C++, examinaremos el patrón productor-consumidor y aprenderemos a construir un thread pool simple.



1. Cola básica basada en std::queue



La cola de tareas más simple es un contenedor en el que se pueden agregar elementos (tareas) y extraerlos en orden FIFO. En C++, std::queue de la biblioteca estándar es perfecta para esto. Sin embargo, al trabajar con múltiples hilos, una cola normal no es segura: el acceso simultáneo desde diferentes hilos provoca condiciones de carrera.



Consideremos un ejemplo básico de una cola sincronizada usando un mutex y una variable de condición:



#include <queue>#include <mutex>#include <condition_variable>#include <functional>

class TaskQueue {public: using Task = std::function<void()>;

void push(Task task) { std::lock_guard<std::mutex> lock(mutex_); queue_.push(std::move(task)); cond_var_.notify_one(); }

Task pop() { std::unique_lock<std::mutex> lock(mutex_); cond_var_.wait(lock, [this]() { return !queue_.empty(); }); Task task = std::move(queue_.front()); queue_.pop(); return task; }

bool empty() const { std::lock_guard<std::mutex> lock(mutex_); return queue_.empty(); }

private: std::queue<Task> queue_; mutable std::mutex mutex_; std::condition_variable cond_var_;};


En este código, push() agrega una tarea y notifica a un hilo en espera. pop() bloquea el hilo hasta que la cola deje de estar vacía. Este enfoque resuelve el problema de sincronización, pero no escala a múltiples consumidores sin modificaciones.



2. Patrón Productor-Consumidor



El problema clásico del productor-consumidor (producer-consumer) encaja perfectamente con una cola de tareas. Uno o varios hilos productores generan tareas, y los hilos consumidores las ejecutan. Es importante gestionar correctamente el tamaño de la cola para evitar desbordamientos o tiempos de inactividad.



Ejemplo con múltiples consumidores:



#include <thread>#include <vector>#include <iostream>

void worker(TaskQueue& queue, int id) { while (true) { auto task = queue.pop(); if (!task) break; // señal de finalización std::cout << "Worker " << id << " executing task"; task(); }}

int main() { TaskQueue queue; std::vector<std::thread> workers;

// Iniciamos 4 hilos consumidores for (int i = 0; i < 4; ++i) { workers.emplace_back(worker, std::ref(queue), i); }

// El productor agrega tareas for (int i = 0; i < 10; ++i) { queue.push([i]() { std::this_thread::sleep_for(std::chrono::milliseconds(100)); std::cout << "Task " << i << " done"; }); }

// Señal de finalización (tarea nullptr) for (auto& w : workers) { queue.push(nullptr); }

for (auto& w : workers) { w.join(); } return 0;}


Aquí, cada consumidor recibe una tarea, la ejecuta y espera la siguiente. La señal de finalización (nullptr) detiene el bucle. Este patrón se utiliza a menudo en aplicaciones de servidor, manejadores de eventos y motores de juegos.



3. Thread Pool — un pool de hilos para la cola de tareas



Un thread pool (pool de hilos) es una capa superior sobre la cola de tareas que gestiona

Blogs

Book Recommendations