Rust Admin

Como Usar Threads em Rust: Criando e Sincronizando Execução Paralela em Produção Já leu

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 permite iniciar uma nova thread, retornando um identificador ( ) 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. Neste exemplo, a closure captura o ambiente implicitamente. O é 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 protege dados contra acesso simultâneo, enquanto (Atomic Reference Counting) permite propriedade compartilhada entre threads. Essa combinação, , é o padrão fundamental para dados mutáveis compartilhados. O incrementa o contador de referências sem clonar os dados reais.

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.

Referências


Artigos relacionados