Introdução: por que filas de tarefas em Rust?
\n\nNo desenvolvimento moderno de sistemas de alta carga, as filas de tarefas (task queues) são um padrão fundamental. Elas permitem separar a geração de tarefas da sua execução, balancear a carga, escalar workers e construir aplicações tolerantes a falhas. Rust, com seu controle rigoroso de memória e ausência de coletor de lixo, oferece capacidades únicas para implementar filas eficientes.
\n\nNeste artigo, abordaremos três abordagens práticas para trabalhar com filas de tarefas em Rust:
\n- \n
- Uso de canais padrão
mpsc(multi-produtor, único-consumidor); \n - Filas multithread com a biblioteca
crossbeam; \n - Filas assíncronas com
tokio. \n
Cada abordagem será acompanhada por um exemplo de código funcional com explicações. Você aprenderá como escolher a ferramenta certa para a tarefa específica e evitar erros comuns.
\n\n1. Canais clássicos: std::sync::mpsc
\n\nA biblioteca padrão do Rust fornece o módulo std::sync::mpsc — uma fila multithread com um único consumidor. Esta é a escolha ideal quando você tem vários produtores (threads que enviam tarefas) e um worker (thread que as executa).
Exemplo simples: pool de um único worker
\n\nuse std::sync::mpsc;\nuse std::thread;\nuse std::time::Duration;\n\nfn main() {\n // Criamos um canal: sender (remetente) e receiver (receptor)\n let (tx, rx) = mpsc::channel::<String>();\n\n // Iniciamos a thread worker\n let worker = thread::spawn(move || {\n for task in rx {\n println!("[Worker] Executando tarefa: {}", task);\n thread::sleep(Duration::from_millis(500));\n println!("[Worker] Tarefa '{}' concluída!", task);\n }\n });\n\n // Criamos vários produtores (em uma única thread para simplificar)\n let producer = thread::spawn(move || {\n let tasks = vec![\n "Processar arquivo A",\n "Enviar email",\n "Atualizar banco de dados",\n "Gerar relatório",\n ];\n for task in tasks {\n println!("[Produtor] Enviando: {}", task);\n tx.send(task.to_string()).unwrap();\n thread::sleep(Duration::from_millis(200));\n }\n });\n\n producer.join().unwrap();\n // Após o sender (tx) ser destruído, o receiver (rx) receberá None e o loop terminará\n drop(tx); // fechamos o canal do lado do produtor\n worker.join().unwrap();\n}\n\nCaracterísticas:
\n- \n
- O canal
mpscé bufferizado — as tarefas não bloqueiam o remetente até que o buffer esteja cheio. \n - Apenas um receptor — esta é uma limitação, mas garante simplicidade e segurança de thread. \n
- Quando todos os remetentes falham, o canal é fechado automaticamente. \n
Vários workers: distribuição manual
\n\nSe você precisa de vários workers, mpsc não é adequado diretamente. A solução é criar um único canal e várias threads que competirão para receber tarefas. No entanto, isso requer cuidado: Receiver não implementa Sync, então ele precisa ser envolvido em Mutex ou 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 // Capturamos o mutex e recebemos a tarefa\n let rx = rx.lock().unwrap();\n rx.recv()\n };\n match task {\n Ok(task) => {\n println!("[Work