Introdução às Filas de Tarefas em Rust
\n\nFilas de tarefas são um dos padrões fundamentais da programação concorrente. Elas permitem organizar a transferência eficiente de trabalho entre threads, simplificando a arquitetura de aplicações. Em Rust, com seu sistema rigoroso de propriedade e tipos, trabalhar com filas exige uma abordagem especial, mas oferece em troca uma segurança e desempenho incríveis.
\n\nNeste artigo, vamos explorar como criar e usar filas de tarefas em Rust do zero. Você aprenderá sobre as ferramentas padrão da linguagem, como canais mpsc (multi-producer, single-consumer), e conhecerá bibliotecas populares para processamento assíncrono. O material é voltado para desenvolvedores iniciantes que já conhecem o básico da sintaxe do Rust.
Fundamentos: o que é uma fila de tarefas e por que usá-la
\n\nUma fila de tarefas é uma estrutura de dados que opera pelo princípio FIFO (First In, First Out). No contexto de multithreading, ela permite que algumas threads (produtoras) adicionem tarefas, enquanto outras (consumidoras) as retiram e executam.
\n\nPrincipais vantagens do uso de filas:
\n\n- \n
- Separação de responsabilidades — a lógica de geração de tarefas é separada da sua execução. \n
- Balanceamento de carga — é possível executar várias threads consumidoras para processamento paralelo. \n
- Controle de fluxo de dados — a fila suaviza picos de carga, prevenindo sobrecarga do sistema. \n
- Facilidade de teste — cada componente pode ser testado isoladamente. \n
Em Rust, a biblioteca padrão fornece o módulo std::sync::mpsc para criar canais com um único receptor. Esta é a ferramenta ideal para começar.
Criando uma fila simples com mpsc
\n\nVamos começar com um exemplo básico: um produtor envia números, um consumidor os processa.
\n\nuse std::sync::mpsc;\nuse std::thread;\nuse std::time::Duration;\n\nfn main() {\n // Criando o canal: tx — transmissor, rx — receptor\n let (tx, rx) = mpsc::channel();\n\n // Iniciando a thread produtora\n thread::spawn(move || {\n for i in 1..=5 {\n println!("Enviando: {}", i);\n tx.send(i).unwrap();\n thread::sleep(Duration::from_millis(500));\n }\n });\n\n // Thread consumidora (thread principal)\n for received in rx {\n println!("Recebido: {}", received);\n }\n}\n\n\nNeste exemplo, tx (transmitter) pode ser clonado para múltiplos produtores, enquanto rx (receiver) é o único consumidor. O método send retorna um Result, que tratamos com unwrap() — em código real, é melhor tratar erros de forma mais adequada.
Múltiplos produtores
\n\nPara criar várias threads produtoras, clonamos o transmissor:
\n\nuse std::sync::mpsc;\nuse std::thread;\n\nfn main() {\n let (tx, rx) = mpsc::channel();\n let tx1 = tx.clone();\n\n thread::spawn(move || {\n for i in 1..=3 {\n tx1.send(format!("Thread 1: mensagem {}", i)).unwrap();\n }\n });\n\n thread::spawn(move || {\n for i in 1..=3 {\n tx.send(format!("Thread 2: mensagem {}", i)).unwrap();\n }\n });\n\n for received in rx {\n println!("{}", received);\n }\n}\n\n\nObserve: o tx original é movido para a segunda thread, por isso o clonamos antes da movimentação.
Técnicas avançadas: tipos de tarefas e tratamento de erros
\n\nEm aplicações reais, as tarefas raramente são números simples. Geralmente são estruturas contendo dados e lógica. Vamos ver como organizar uma fila com tarefas de diferentes tipos.
\n\nEnumeração para diferentes tipos de tarefas
\n\nuse std::sync::mpsc;\nuse std::thread;\n\n#[derive(Debug)]\nenum Task {\n Compute { a: