Pular para conteúdo

Aula 13 - Concorrência Segura 🧵

Objetivo

Objetivo: Entender como o Rust garante a segurança em ambientes multi-thread, eliminando as temidas "corridas de dados" (data races) em tempo de compilação.


1. Concorrência Sem Medo 🛡️

O Rust utiliza seu sistema de Ownership e Tipagem para garantir que a memória seja acessada de forma segura por múltiplas threads.

Criando Threads

use std::thread;
use std::time::Duration;

fn main() {
    thread::spawn(|| {
        for i in 1..10 {
            println!("Oi da thread spawned: {}", i);
            thread::sleep(Duration::from_millis(1));
        }
    });

    println!("Oi da thread principal!");
}

2. Passagem de Mensagens: Channels 📡

O Rust segue o lema: "Não comunique compartilhando memória; compartilhe memória comunicando". Os Channels (Canais) são a ferramenta para isso.

use std::sync::mpsc; // multiple producer, single consumer
use std::thread;

fn main() {
    let (tx, rx) = mpsc::channel();

    thread::spawn(move || {
        let val = String::from("oi");
        tx.send(val).unwrap();
    });

    let recebido = rx.recv().unwrap();
    println!("Recebido: {}", recebido);
}

3. Estado Compartilhado: Mutex e Arc 🔒

Quando você precisa compartilhar memória, o Rust exige o uso de ferramentas de sincronização.

  • Mutex: Garante que apenas uma thread acesse os dados por vez.
  • Arc: Um ponteiro com contagem de referências atômicas, permitindo múltiplos donos em threads diferentes.
use std::sync::{Arc, Mutex};
use std::thread;

fn main() {
    let contador = Arc::new(Mutex::new(0));
    let mut handles = vec![];

    for _ in 0..10 {
        let contador = Arc::clone(&contador);
        let handle = thread::spawn(move || {
            let mut num = contador.lock().unwrap();
            *num += 1;
        });
        handles.push(handle);
    }

    for handle in handles { handle.join().unwrap(); }
    println!("Resultado: {}", *contador.lock().unwrap());
}

4. Send e Sync: O Segredo das Traits 🧬

  • Send: Permite que a posse do tipo seja transferida entre threads.
  • Sync: Permite que o tipo seja acessado por múltiplas threads através de referências.

A maioria dos tipos básicos do Rust são Send e Sync. O compilador impede que você passe tipos inseguros para outras threads!


5. Visualização: Modelo de Comunicação 🔄

graph LR
    T1[Thread 1] -- manda mensagem --> Canal((Canal / mpsc))
    T2[Thread 2] -- manda mensagem --> Canal
    Canal -- entrega --> Main[Thread Principal]
    style Canal fill:#f9f,stroke:#333

6. Mini-Projeto: Processador de Imagens Multi-thread 🖼️

Crie uma simulação de processamento: 1. Um vetor de "IDs de imagens" (pode ser apenas números). 2. Use Arc e Mutex para manter uma lista de "Imagens Processadas". 3. Crie 4 threads que "processam" as imagens (use thread::sleep para simular trabalho). 4. Use um Channel para que cada thread avise a thread principal quando terminar uma imagem. 5. A thread principal deve exibir o progresso em tempo real.


7. Exercício de Fixação 🧠

  1. Por que o método mpsc::channel se chama "mpsc" (Multiple Producer, Single Consumer)?
  2. O que o método lock() de um Mutex faz e o que acontece se outra thread já tiver o lock?
  3. Explique a diferença entre Arc e Rc (da aula de ponteiros inteligentes, se vista, ou pesquise a diferença atômica).

Próxima Aula: Vamos aplicar tudo em um Projeto CLI Profissional! 💻