Executar Threads em Rust
Rust torna a programação com threads segura em tempo de compilação. O sistema de ownership garante que você não terá data races, e a API de threads da biblioteca padrão é direta e eficiente.
Thread básica com thread::spawn
Crie uma thread e aguarde sua conclusão com join():
use std::thread;
use std::time::Duration;
fn main() {
println!("Thread principal iniciada");
// Criar uma nova thread
let handle = thread::spawn(|| {
for i in 1..=5 {
println!(" Thread filha: contagem {}", i);
thread::sleep(Duration::from_millis(100));
}
42 // valor de retorno
});
// A thread principal continua executando
for i in 1..=3 {
println!("Thread principal: contagem {}", i);
thread::sleep(Duration::from_millis(150));
}
// Aguardar a thread filha e pegar o resultado
let resultado = handle.join().unwrap();
println!("Thread filha retornou: {}", resultado);
}
Saída (a ordem pode variar):
Thread principal iniciada
Thread principal: contagem 1
Thread filha: contagem 1
Thread filha: contagem 2
Thread principal: contagem 2
Thread filha: contagem 3
Thread filha: contagem 4
Thread principal: contagem 3
Thread filha: contagem 5
Thread filha retornou: 42
Move closures — transferir dados para threads
Use a keyword move para transferir ownership de variáveis para a thread:
use std::thread;
fn main() {
let dados = vec![1, 2, 3, 4, 5];
let mensagem = String::from("Olá da thread!");
// `move` transfere ownership de `dados` e `mensagem` para a thread
let handle = thread::spawn(move || {
println!("Mensagem: {}", mensagem);
let soma: i32 = dados.iter().sum();
println!("Soma: {}", soma);
soma
});
// `dados` e `mensagem` não podem mais ser usados aqui
// println!("{}", mensagem); // ERRO de compilação!
let resultado = handle.join().unwrap();
println!("Resultado: {}", resultado);
}
Saída:
Mensagem: Olá da thread!
Soma: 15
Resultado: 15
Múltiplas threads com coleta de resultados
Lance várias threads e colete seus resultados:
use std::thread;
fn calcular_fatorial(n: u64) -> u64 {
(1..=n).product()
}
fn main() {
let numeros = vec![5, 8, 10, 12, 15];
// Criar uma thread para cada cálculo
let handles: Vec<_> = numeros
.into_iter()
.map(|n| {
thread::spawn(move || {
let resultado = calcular_fatorial(n);
println!(" {}! = {}", n, resultado);
(n, resultado)
})
})
.collect();
// Coletar resultados
let resultados: Vec<(u64, u64)> = handles
.into_iter()
.map(|h| h.join().unwrap())
.collect();
println!("\nResultados coletados:");
for (n, fatorial) in &resultados {
println!(" {}! = {}", n, fatorial);
}
}
Saída:
5! = 120
8! = 40320
10! = 3628800
12! = 479001600
15! = 1307674368000
Resultados coletados:
5! = 120
8! = 40320
10! = 3628800
12! = 479001600
15! = 1307674368000
Dados compartilhados com Arc e Mutex
Compartilhe dados entre threads com segurança:
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
// Arc = referência compartilhada thread-safe
// Mutex = acesso exclusivo (mutual exclusion)
let contador = Arc::new(Mutex::new(0));
let resultados = Arc::new(Mutex::new(Vec::new()));
let mut handles = vec![];
for id in 0..5 {
let contador = Arc::clone(&contador);
let resultados = Arc::clone(&resultados);
let handle = thread::spawn(move || {
// Lock do mutex para acessar o valor
let mut num = contador.lock().unwrap();
*num += 1;
let valor_atual = *num;
drop(num); // liberar o lock cedo
// Simular trabalho
let resultado = format!("Thread {} processou (contador={})", id, valor_atual);
// Adicionar ao vetor compartilhado
resultados.lock().unwrap().push(resultado);
});
handles.push(handle);
}
// Aguardar todas as threads
for handle in handles {
handle.join().unwrap();
}
println!("Contador final: {}", *contador.lock().unwrap());
println!("\nResultados:");
for r in resultados.lock().unwrap().iter() {
println!(" {}", r);
}
}
Saída:
Contador final: 5
Resultados:
Thread 0 processou (contador=1)
Thread 1 processou (contador=2)
Thread 2 processou (contador=3)
Thread 3 processou (contador=4)
Thread 4 processou (contador=5)
Canais (channels) com mpsc
Comunique entre threads usando canais:
use std::sync::mpsc;
use std::thread;
use std::time::Duration;
fn main() {
let (tx, rx) = mpsc::channel();
// Múltiplos produtores (clone do transmissor)
for id in 0..3 {
let tx = tx.clone();
thread::spawn(move || {
let mensagens = vec![
format!("Olá do produtor {}", id),
format!("Dados do produtor {}", id),
format!("Fim do produtor {}", id),
];
for msg in mensagens {
tx.send(msg).unwrap();
thread::sleep(Duration::from_millis(100));
}
});
}
// Dropar o transmissor original (senão o loop abaixo nunca termina)
drop(tx);
// Consumidor — recebe todas as mensagens
let mut total = 0;
for mensagem in rx {
println!("Recebido: {}", mensagem);
total += 1;
}
println!("\nTotal de mensagens: {}", total);
}
Saída (ordem pode variar):
Recebido: Olá do produtor 0
Recebido: Olá do produtor 1
Recebido: Olá do produtor 2
Recebido: Dados do produtor 0
Recebido: Dados do produtor 1
Recebido: Dados do produtor 2
Recebido: Fim do produtor 0
Recebido: Fim do produtor 1
Recebido: Fim do produtor 2
Total de mensagens: 9
Thread pool com rayon
Para paralelismo de dados, a crate rayon é a escolha ideal:
Cargo.toml:
[dependencies]
rayon = "1"
use rayon::prelude::*;
fn eh_primo(n: u64) -> bool {
if n < 2 { return false; }
if n < 4 { return true; }
if n % 2 == 0 || n % 3 == 0 { return false; }
let mut i = 5;
while i * i <= n {
if n % i == 0 || n % (i + 2) == 0 { return false; }
i += 6;
}
true
}
fn main() {
let numeros: Vec<u64> = (1..=100_000).collect();
// par_iter() — iterador paralelo automático
let primos: Vec<u64> = numeros
.par_iter()
.filter(|&&n| eh_primo(n))
.copied()
.collect();
println!("Primos encontrados: {}", primos.len());
println!("Últimos 10: {:?}", &primos[primos.len()-10..]);
// par_chunks — processar em blocos paralelos
let dados: Vec<i32> = (1..=1000).collect();
let somas: Vec<i32> = dados
.par_chunks(100)
.map(|bloco| bloco.iter().sum())
.collect();
println!("\nSomas por bloco (10 blocos): {:?}", somas);
println!("Soma total: {}", somas.iter().sum::<i32>());
}
Saída:
Primos encontrados: 9592
Últimos 10: [99901, 99907, 99923, 99929, 99961, 99971, 99989, 99991, 99997, 100003]
Somas por bloco (10 blocos): [5050, 15050, 25050, 35050, 45050, 55050, 65050, 75050, 85050, 95050]
Soma total: 500500
Veja também
- Async/Await Básico — concorrência assíncrona (I/O bound)
- Fazer Requisição HTTP — requisições paralelas
- Ler Arquivo em Rust — processe arquivos em threads
- Ler Arquivo CSV — paralelize processamento de dados
- Conectar ao PostgreSQL — connection pools para threads
- Documentação oficial:
std::thread