Introducción: ¿por qué colas de tareas en Rust?
\n\nEn el desarrollo moderno de sistemas de alta carga, las colas de tareas (task queues) son un patrón fundamental. Permiten separar la generación de tareas y su ejecución, balancear la carga, escalar los workers y construir aplicaciones tolerantes a fallos. Rust, con su estricto control de memoria y la ausencia de un recolector de basura, ofrece capacidades únicas para implementar colas eficientes.
\n\nEn este artículo, analizaremos tres enfoques prácticos para trabajar con colas de tareas en Rust:
\n- \n
- Uso de canales estándar
mpsc(multi-productor, único-consumidor); \n - Colas multiproceso con la biblioteca
crossbeam; \n - Colas asíncronas con
tokio. \n
Cada enfoque estará acompañado de un ejemplo de código funcional con explicaciones. Aprenderá a elegir la herramienta adecuada para cada tarea específica y a evitar errores típicos.
\n\n1. Canales clásicos: std::sync::mpsc
\n\nLa biblioteca estándar de Rust proporciona el módulo std::sync::mpsc — una cola multiproceso con un único consumidor. Es la elección ideal cuando tiene varios productores (hilos que envían tareas) y un solo worker (hilo que las ejecuta).
Ejemplo simple: pool de un solo worker
\n\nuse std::sync::mpsc;\nuse std::thread;\nuse std::time::Duration;\n\nfn main() {\n // Creamos el canal: sender (remitente) y receiver (receptor)\n let (tx, rx) = mpsc::channel::<String>();\n\n // Iniciamos el hilo worker\n let worker = thread::spawn(move || {\n for task in rx {\n println!("[Worker] Ejecutando tarea: {}", task);\n thread::sleep(Duration::from_millis(500));\n println!("[Worker] ¡Tarea '{}' completada!", task);\n }\n });\n\n // Creamos varios productores (en un solo hilo por simplicidad)\n let producer = thread::spawn(move || {\n let tasks = vec![\n "Procesar archivo A",\n "Enviar correo electrónico",\n "Actualizar base de datos",\n "Generar informe",\n ];\n for task in tasks {\n println!("[Productor] Enviando: {}", task);\n tx.send(task.to_string()).unwrap();\n thread::sleep(Duration::from_millis(200));\n }\n });\n\n producer.join().unwrap();\n // Después de que el sender (tx) sea destruido, el receiver (rx) recibirá None y el ciclo terminará\n drop(tx); // cerramos el canal desde el lado del productor\n worker.join().unwrap();\n}\n\nCaracterísticas:
\n- \n
- El canal
mpscestá almacenado en búfer: las tareas no bloquean al remitente hasta que el búfer esté lleno. \n - Solo un receptor: es una limitación, pero garantiza simplicidad y seguridad en la concurrencia. \n
- Cuando todos los remitentes fallan, el canal se cierra automáticamente. \n
Varios workers: distribución manual
\n\nSi necesita varios workers, mpsc no es adecuado directamente. La solución es crear un canal y varios hilos que compitan por obtener las tareas. Sin embargo, esto requiere cuidado: Receiver no implementa Sync, por lo que debe envolverse en un Mutex o Arc.
use std::sync::{mpsc, Arc, Mutex};\nuse std::thread;\nuse std::time::Duration;\n\nfn main() {\n let (tx, rx) = mpsc::channel::<String>();\n let rx = Arc::new(Mutex::new(rx));\n\n let mut workers = vec![];\n for id in 0..3 {\n let rx = Arc::clone(&rx);\n let worker = thread::spawn(move || {\n loop {\n let task = {\n // Tomamos el mutex y obtenemos la tarea\n let rx = rx.lock().unwrap();\n rx.recv()\n };\n match task {\n Ok(task) => {\n println!("[Work