O que faz e quando usar
O módulo std::thread fornece a API para criar e gerenciar threads nativas do sistema operacional em Rust. Cada thread criada com thread::spawn corresponde a uma thread real do SO (1:1 threading), com sua própria pilha de execução (stack). Threads são a forma mais direta de alcançar paralelismo em Rust — executar código simultaneamente em múltiplos núcleos da CPU.
Use std::thread quando:
- Você precisa executar trabalho CPU-bound em paralelo (cálculos, processamento de dados).
- Quer executar operações bloqueantes sem travar a thread principal.
- Precisa de paralelismo verdadeiro (não apenas concorrência cooperativa como async/await).
- Quer compartilhar dados entre tarefas usando
Arc<Mutex<T>>ou canaismpsc.
Evite criar threads para cada requisição de I/O — para I/O-bound, considere async/await com Tokio. Cada thread consome ~8KB de stack por padrão, então milhares de threads podem esgotar memória.
Tipos e Funções Principais
| Item | Descrição |
|---|---|
thread::spawn(f) | Cria uma nova thread executando a closure f |
JoinHandle<T> | Handle para aguardar a thread e obter seu resultado |
JoinHandle::join() | Bloqueia até a thread terminar, retorna Result<T> |
thread::sleep(dur) | Suspende a thread atual por pelo menos dur |
thread::yield_now() | Cede o processador para outra thread |
thread::current() | Retorna um Thread representando a thread atual |
thread::Builder | Construtor para configurar nome e tamanho de stack |
thread::park() | Suspende a thread até ser “desparkada” |
thread::Thread::unpark() | Acorda uma thread suspensa com park() |
Exemplos de Código
Criando uma thread básica com spawn
A função thread::spawn recebe uma closure e retorna um JoinHandle:
use std::thread;
use std::time::Duration;
fn main() {
// Criar uma nova thread
let handle = thread::spawn(|| {
for i in 1..=5 {
println!("[filha] contagem: {}", i);
thread::sleep(Duration::from_millis(100));
}
42 // valor de retorno da thread
});
// A thread principal continua executando em paralelo
for i in 1..=3 {
println!("[principal] contagem: {}", i);
thread::sleep(Duration::from_millis(150));
}
// join() bloqueia até a thread terminar
// Retorna Result<T, Box<dyn Any + Send>>
let resultado = handle.join().expect("Thread entrou em pânico");
println!("Thread filha retornou: {}", resultado);
}
Move closures — transferindo ownership
Quando a closure precisa usar dados da thread que a criou, use move para transferir a posse:
use std::thread;
fn main() {
let dados = vec![10, 20, 30, 40, 50];
let nome = String::from("processamento");
// `move` transfere ownership de `dados` e `nome`
let handle = thread::spawn(move || {
println!("Tarefa: {}", nome);
let soma: i32 = dados.iter().sum();
println!("Soma: {}", soma);
soma
});
// dados e nome NÃO estão mais disponíveis aqui!
// println!("{}", nome); // ERRO: value borrowed here after move
let resultado = handle.join().unwrap();
println!("Resultado: {}", resultado);
}
Erro comum — esquecer move:
use std::thread;
fn main() {
let mensagem = String::from("olá");
// ERRO DE COMPILAÇÃO: closure may outlive the current function
// let handle = thread::spawn(|| {
// println!("{}", mensagem);
// });
// Correto: usar move
let handle = thread::spawn(move || {
println!("{}", mensagem);
});
handle.join().unwrap();
}
O compilador exige move porque a thread criada pode viver mais que o escopo onde mensagem foi declarada.
Builder — configurando nome e stack
O thread::Builder permite personalizar a thread antes de criá-la:
use std::thread;
fn main() {
let builder = thread::Builder::new()
.name("worker-01".into()) // nome da thread (aparece em logs/debug)
.stack_size(4 * 1024 * 1024); // stack de 4MB (padrão: ~8MB)
let handle = builder.spawn(|| {
let thread_atual = thread::current();
println!(
"Executando na thread: {:?}",
thread_atual.name().unwrap_or("sem nome")
);
// Trabalho pesado que pode precisar de mais stack
let resultado = calcular_recursivo(20);
println!("Resultado: {}", resultado);
resultado
}).expect("Falha ao criar thread");
let valor = handle.join().unwrap();
println!("Thread nomeada retornou: {}", valor);
}
fn calcular_recursivo(n: u64) -> u64 {
if n <= 1 { return n; }
calcular_recursivo(n - 1) + calcular_recursivo(n - 2)
}
thread::sleep e thread::yield_now
use std::thread;
use std::time::{Duration, Instant};
fn main() {
// sleep: suspende por pelo menos a duração especificada
let inicio = Instant::now();
thread::sleep(Duration::from_millis(100));
println!("sleep durou: {:?}", inicio.elapsed());
// yield_now: cede o processador voluntariamente
// Útil em loops apertados para dar chance a outras threads
let handle = thread::spawn(|| {
for i in 0..5 {
println!("Trabalhando... {}", i);
thread::yield_now(); // dá chance a outras threads
}
});
handle.join().unwrap();
}
thread::current — identificando a thread
use std::thread;
fn quem_sou_eu() {
let atual = thread::current();
println!(
"Thread: {:?}, ID: {:?}",
atual.name().unwrap_or("anônima"),
atual.id()
);
}
fn main() {
quem_sou_eu(); // thread principal
let handle = thread::Builder::new()
.name("minha-thread".into())
.spawn(quem_sou_eu)
.unwrap();
handle.join().unwrap();
}
Criando múltiplas threads e coletando resultados
use std::thread;
fn main() {
let dados = vec![
vec![1, 2, 3, 4, 5],
vec![10, 20, 30],
vec![100, 200, 300, 400],
];
// Criar uma thread para processar cada vetor
let handles: Vec<_> = dados
.into_iter()
.enumerate()
.map(|(id, chunk)| {
thread::spawn(move || {
let soma: i32 = chunk.iter().sum();
println!("Thread {}: soma = {}", id, soma);
soma
})
})
.collect();
// Coletar todos os resultados
let resultados: Vec<i32> = handles
.into_iter()
.map(|h| h.join().unwrap())
.collect();
let total: i32 = resultados.iter().sum();
println!("Total de todas as threads: {}", total);
}
Tratando panics em threads
Quando uma thread entra em pânico, o panic fica contido naquela thread. join() retorna Err se a thread deu panic:
use std::thread;
fn main() {
let handle = thread::spawn(|| {
panic!("Algo deu muito errado!");
});
// O panic NÃO se propaga para a thread principal automaticamente
match handle.join() {
Ok(valor) => println!("Thread retornou: {:?}", valor),
Err(panic_info) => {
// Tentar extrair a mensagem de panic
if let Some(msg) = panic_info.downcast_ref::<&str>() {
eprintln!("Thread entrou em pânico: {}", msg);
} else if let Some(msg) = panic_info.downcast_ref::<String>() {
eprintln!("Thread entrou em pânico: {}", msg);
} else {
eprintln!("Thread entrou em pânico com valor desconhecido");
}
}
}
println!("Thread principal continua normalmente.");
}
Padrões Comuns
Padrão: dividir trabalho entre N threads
Thread Principal
|
+----|----+----+
| | | |
[T0] [T1] [T2] [T3] <-- cada thread processa parte dos dados
| | | |
+----|----+----+
|
Coleta resultados
use std::thread;
fn main() {
let dados: Vec<u64> = (1..=1_000_000).collect();
let num_threads = 4;
let tamanho_chunk = dados.len() / num_threads;
let handles: Vec<_> = dados
.chunks(tamanho_chunk)
.map(|chunk| {
let chunk = chunk.to_vec(); // clonar para ownership
thread::spawn(move || -> u64 {
chunk.iter().sum()
})
})
.collect();
let total: u64 = handles
.into_iter()
.map(|h| h.join().unwrap())
.sum();
println!("Soma de 1 a 1.000.000: {}", total);
// Soma de 1 a 1.000.000: 500000500000
}
Anti-padrão: criar threads demais
// RUIM: uma thread por item — overhead enorme!
// fn processar_ruim(itens: Vec<String>) {
// let handles: Vec<_> = itens.into_iter().map(|item| {
// thread::spawn(move || { /* trabalho mínimo */ })
// }).collect();
// }
// BOM: dividir em chunks proporcionais ao número de CPUs
fn processar_bom(itens: Vec<String>) {
let num_cpus = thread::available_parallelism()
.map(|n| n.get())
.unwrap_or(4);
let chunk_size = (itens.len() + num_cpus - 1) / num_cpus;
let handles: Vec<_> = itens
.chunks(chunk_size)
.map(|chunk| {
let chunk = chunk.to_vec();
std::thread::spawn(move || {
for item in &chunk {
println!("Processando: {}", item);
}
})
})
.collect();
for h in handles {
h.join().unwrap();
}
}
fn main() {
let itens: Vec<String> = (0..100).map(|i| format!("item-{}", i)).collect();
processar_bom(itens);
}
Garantias de Thread Safety
thread::spawnexige que a closure sejaSend + 'static. Isso significa que todos os valores capturados devem ser transferíveis entre threads (Send) e não podem conter referências com lifetime limitado ('static).- Se você precisa compartilhar referências sem
'static, usethread::scope(Rust 1.63+). - Panics em threads filhas são isolados — não derrubam a thread principal a menos que você propague com
join().unwrap(). - O
JoinHandleimplementaSend, então pode ser transferido entre threads.
Veja Também
- Mutex e RwLock em Rust — proteger dados compartilhados entre threads
- Channels em Rust: mpsc — comunicação entre threads por mensagens
- Scoped Threads — threads que podem referenciar dados locais
- Send e Sync — traits de segurança entre threads
- Padrões de Thread Safety — padrões avançados
- Concorrência em Rust — tutorial completo de concorrência
- Executar Threads em Rust — receita prática
- Documentação oficial:
std::thread