Criando Threads em Rust
A criação de threads em Rust é simples e segura graças ao sistema de tipos da linguagem. A função std::thread::spawn() permite iniciar uma nova thread, retornando um identificador (JoinHandle) que você pode usar para aguardar sua conclusão. Diferentemente de linguagens como C ou Java, Rust força você a pensar sobre propriedade e ciclo de vida do código que executa em paralelo, eliminando dados races em tempo de compilação.
use std::thread;
use std::time::Duration;
fn main() {
let handle = thread::spawn(|| {
for i in 1..=5 {
println!("Thread: {}", i);
thread::sleep(Duration::from_millis(100));
}
});
for i in 1..=3 {
println!("Main: {}", i);
thread::sleep(Duration::from_millis(150));
}
handle.join().unwrap(); // Aguarda conclusão
println!("Threads finalizadas!");
}
Neste exemplo, a closure captura o ambiente implicitamente. O join() é essencial — ele bloqueia a thread principal até que a spawned thread termine. Sem ele, o programa poderia encerrar antes da thread completar seu trabalho.
Sincronização com Mutex e Arc
Quando múltiplas threads precisam acessar dados compartilhados, você deve usar sincronização. O Mutex<T> protege dados contra acesso simultâneo, enquanto Arc<T> (Atomic Reference Counting) permite propriedade compartilhada entre threads. Essa combinação, Arc<Mutex<T>>, é o padrão fundamental para dados mutáveis compartilhados.
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
let counter = Arc::new(Mutex::new(0));
let mut handles = vec![];
for _ in 0..5 {
let counter_clone = Arc::clone(&counter);
let handle = thread::spawn(move || {
let mut num = counter_clone.lock().unwrap();
*num += 1;
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
println!("Resultado: {}", *counter.lock().unwrap());
}
O Arc::clone() incrementa o contador de referências sem clonar os dados reais. O lock() tenta adquirir um lock exclusivo; se outra thread já possui, bloqueia até liberação. O unwrap() panic se ocorrer poison (quando uma thread morre enquanto holds o lock), mas para código robusto, você deveria tratar esse cenário. Este código imprime Resultado: 5, demonstrando sincronização correta.
Channels para Comunicação entre Threads
Para padrões produtor-consumidor, mpsc::channel() (multiple producer, single consumer) oferece uma forma elegante de comunicação sem locks explícitos. O sender envia mensagens; o receiver as consome. Esse padrão evita compartilhamento de estado mutável, alinhado com princípios de concorrência segura.
use std::sync::mpsc;
use std::thread;
fn main() {
let (tx, rx) = mpsc::channel();
let handle = thread::spawn(move || {
let valores = vec![1, 2, 3, 4, 5];
for val in valores {
tx.send(val).unwrap();
thread::sleep(std::time::Duration::from_millis(100));
}
});
for received in rx {
println!("Recebido: {}", received);
}
handle.join().unwrap();
}
Aqui, o sender (tx) é movido para a thread. Cada valor é enviado e o loop receptor itera sobre mensagens até o sender ser dropado (indicando fim da comunicação). Channels são preferíveis a Mutex para cenários de comunicação porque são mais expressivos e evitam deadlocks comuns. O unwrap() no send() panic se o receiver foi dropado; versões robustas verificam Result.
Padrões Avançados e Boas Práticas
Para aplicações complexas, considere thread pools com bibliotecas como rayon ou tokio. O rayon simplifica paralelismo de dados (map-reduce), enquanto tokio oferece async/await para I/O-bound tasks, usando menos threads que código blocking. Para deadlock prevention, sempre adquira locks na mesma ordem globalmente e use timeouts quando apropriado.
use rayon::prelude::*;
fn main() {
let data = vec![1, 2, 3, 4, 5, 6, 7, 8];
let result: Vec<i32> = data
.par_iter()
.map(|x| x * x)
.collect();
println!("{:?}", result); // [1, 4, 9, 16, 25, 36, 49, 64]
}
Este exemplo com rayon distribui o trabalho entre threads automaticamente — não há spawn manual. É ideal para CPU-bound tasks. Rust força você a pensar sobre segurança, mas oferece ferramentas poderosas para explorar paralelismo sem sacrificar confiabilidade.
Conclusão
Rust torna concorrência segura através de três mecanismos principais: propriedade e ciclo de vida previnem data races em compilação; Mutex + Arc sincronizam dados compartilhados com segurança; channels comunicam entre threads eliminando estado mutável compartilhado. O sistema de tipos força boas práticas — código que compila é seguro para dados races. Domine spawn(), join(), Mutex, Arc e mpsc::channel() e você terá fundação sólida para qualquer aplicação concorrente.