Concorrência e Multithreading em Rust: do A ao Z
Os aplicativos modernos exigem cada vez mais o uso eficiente de processadores multi-core. O Rust oferece uma abordagem única para concorrência e multithreading, combinando alta performance com garantias de segurança de memória em tempo de compilação. Neste artigo, abordaremos os conceitos-chave: desde a criação de threads até o uso de mutexes e canais, com exemplos práticos de código.
1. Fundamentos do multithreading: criação e gerenciamento de threads
Em Rust, as threads são criadas usando a biblioteca padrão std::thread. Cada thread é executada de forma independente, mas pode trocar dados com outras threads.
Exemplo simples de criação de uma thread:
use std::thread;use std::time::Duration;
fn main() { let handle = thread::spawn(|| { for i in 1..10 { println!("Thread: número {}", i); thread::sleep(Duration::from_millis(1)); } });
for i in 1..5 { println!("Thread principal: número {}", i); thread::sleep(Duration::from_millis(1)); }
handle.join().unwrap(); // Aguarda a conclusão da thread}Pontos-chave:
thread::spawnrecebe uma closure que é executada em uma nova thread.- O método
joinbloqueia a thread atual até que a thread especificada termine. - Sem
join, a thread principal pode terminar antes da thread filha.
Para transferir dados entre threads, use closures com move:
use std::thread;
fn main() { let data = vec![1, 2, 3, 4, 5];
let handle = thread::spawn(move || { println!("Dados da thread: {:?}", data); });
handle.join().unwrap(); // println!("{:?}", data); // Erro! data já foi movido}A palavra-chave move transfere a propriedade dos dados para a closure, prevenindo condições de corrida em tempo de compilação.
2. Troca segura de dados: mutexes e bloqueios
Quando várias threads acessam dados compartilhados, surge o risco de condição de corrida. O Rust resolve esse problema com mutexes (Mutex) e tipos atômicos.
Exemplo de uso de Mutex com Arc (contador de referência atômico):
use std::sync::{Arc, Mutex};use std::thread;
fn main() { let counter = Arc::new(Mutex::new(0)); let mut handles = vec![];
for _ in 0..10 { let counter = Arc::clone(&counter); let handle = thread::spawn(move || { let mut num = counter.lock().unwrap(); *num += 1; }); handles.push(handle); }
for handle in handles { handle.join().unwrap(); }
println!("Resultado: {}", *counter.lock().unwrap()); // 10}Detalhes importantes:
Arc<T>permite que várias threads possuam os mesmos dados (clonagem atômica).Mutex::lock()retorna um bloqueio; se o bloqueio já estiver ocupado, a thread espera.- O bloqueio é liberado automaticamente ao sair do escopo.
Para um controle mais refinado, use RwLock, que separa leitores e escritores:
use std::sync::RwLock;
let lock = RwLock::new(5);
// Leitura: várias threads simultaneamente{ let r1 = lock.read().unwrap(); let r2 = lock.read().unwrap(); println!("Lendo: {} e {}", r1, r2);}
// Escrita: apenas uma thread{ let mut w = lock.write().unwrap(); *w += 1; println!("Escrevendo: {}", w);}3. Canais para transmissão de mensagens
Uma alternativa à memória compartilhada é o modelo de atores usando canais. O Rust fornece um canal multiprodutor, um consumidor (mpsc).
Exemplo básico:
u