Executar Threads em Rust

Aprenda como executar threads em Rust com thread::spawn, join, move closures, canais mpsc e thread pools com rayon. Exemplos completos.

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