Rust: Programação Concorrente em Rust

Última atualização: 2026-08-26

Concorrência é a capacidade de um programa de realizar várias tarefas “ao mesmo tempo” — o Rust elimina conflitos de acesso aos dados em tempo de compilação por meio de seus sistemas de propriedade e de tipos, permitindo que você escreva código concorrente que seja ao mesmo tempo eficiente e seguro.

Se um programa de thread único é como “uma pessoa trabalhando na cozinha do início ao fim”, então a multithreading é como “vários chefs trabalhando ao mesmo tempo” — alguns cortando legumes, outros refogando e outros ainda montando os pratos. Mas quando há gente demais na cozinha, a situação pode facilmente ficar caótica: duas pessoas podem tentar pegar a mesma faca ao mesmo tempo, ou alguém pode pegar um ingrediente que outra pessoa já está usando. O modelo de concorrência do Rust é como uma “cozinha com vários chefs e regras” — cada chef tem suas próprias ferramentas, os ingredientes são repassados por canais dedicados e os temperos compartilhados só podem ser usados por uma pessoa por vez.


1. O que você vai aprender


2. A história de um sistema colaborativo de edição de documentos

(1) Problema: Documentação online sem controle de concorrência

A empresa da Luna está desenvolvendo um editor colaborativo de documentos online. As primeiras versões permitiam que apenas uma pessoa editasse por vez, o que gerava reclamações constantes dos membros da equipe:

“Seria ótimo se pudéssemos criar um tópico separado para cada editor, enviar solicitações de modificação por meio de um canal e proteger o conteúdo do documento com um mutex...”

(2) Abordagens ao modelo de concorrência do Rust

RUST
use std::thread;
use std::sync::mpsc;
use std::sync::{Arc, Mutex};
use std::time::Duration;

fn main() {
    // Create a document with shared content(Arc<Mutex<String>>)
    let document = Arc::new(Mutex::new(String::from("# Collaborative Documents\n\n")));

    // Create a channel,Used to transmit edit requests
    let (tx, rx) = mpsc::channel::<String>();

    // Receiving Thread:Processing editing requests on an ongoing basis
    let doc_for_receiver = Arc::clone(&document);
    let receiver = thread::spawn(move || {
        for edit in rx {
            let mut doc = doc_for_receiver.lock().unwrap();
            doc.push_str(&edit);
            doc.push('\n');
            println!("[Receiver] Edits Applied");
        }
    });

    // Simulating an Editor Thread
    let tx1 = tx.clone();
    thread::spawn(move || {
        tx1.send("- Alice: Added the content for Chapter 1".to_string()).unwrap();
    });

    let tx2 = tx.clone();
    thread::spawn(move || {
        tx2.send("- Bob: The title of Chapter 2 has been revised.".to_string()).unwrap();
    });

    // The main thread also sends a message
    tx.send("- Luna: Format Adjustments".to_string()).unwrap();

    // Wait until all editors have finished sending their edits
    thread::sleep(Duration::from_millis(100));
    drop(tx);  // Close the sender

    receiver.join().unwrap();

    // Final Document Content
    let final_doc = document.lock().unwrap();
    println!("\n=== Final Document ===");
    println!("{}", *final_doc);
}

A solução de concorrência do Rust separa claramente as responsabilidades: cada editor é executado em uma thread separada, as solicitações de edição são transmitidas por meio do mpsc::channel e o conteúdo do documento é protegido pelo Mutex. O Arc permite que o Mutex seja compartilhado entre várias threads. O sistema de propriedade garante que não ocorram condições de corrida.


3. Conceitos fundamentais

(1) A Estrutura de Concorrência do Rust

100%
graph TB
    A[Rust Concurrent Programming] --> B[Thread Management]
    A --> C[Messaging]
    A --> D[Shared Status]
    A --> E[Safety Guarantee]

    B --> B1["thread::spawn ||"]
    B --> B2["join() The Wait Is Over"]
    B --> B3["move Closures Transfer Ownership"]

    C --> C1["mpsc::channel"]
    C --> C2["Sender / Receiver"]
    C --> C3["send / recv"]

    D --> D1["Mutex&lt;T&gt; Mutex"]
    D --> D2["lock() Acquire a lock"]
    D --> D3["Arc&lt;T&gt; Atomic Reference Counting"]

    E --> E1["Send: Ownership can be transferred across threads"]
    E --> E2["Sync: References can be shared across threads"]
    E --> E3["Eliminating Data Races at Compile Time"]

(2) Comparação entre três modelos de concorrência

Recurso thread::spawn (thread) mpsc::channel (canal) Arc<Mutex<T>> (estado compartilhado)
Conceito central Unidades de execução independentes Passagem de mensagens Memória compartilhada + bloqueios
Métodos de transferência de dados move: Transferir propriedade por meio de um closure send/recv: Enviar uma mensagem lock: Obter um valor interno
Casos de uso Execução paralela de tarefas independentes Padrão produtor-consumidor Várias threads acessando os mesmos dados
Vantagens Aproveita ao máximo as CPUs multi-core Separa emissores e receptores Compartilha diretamente qualquer tipo
Desvantagens Comunicação complexa entre threads Não é adequado para transferências frequentes de pequenos volumes de dados Possíveis impasses e sobrecarga de desempenho
Recursos do Rust A propriedade evita ponteiros pendentes O compilador garante o uso correto Evita corridas de dados em tempo de compilação

(3) Característica “Enviar e sincronizar”

Característica Significado Condições implementadas automaticamente Tipos não implementados automaticamente
Enviar A propriedade de um tipo pode ser transferida entre threads Implementado automaticamente para a grande maioria dos tipos Rc<T> (contagem de referências não atômica)
Sincronização As referências aos tipos podem ser compartilhadas entre threads Implementada automaticamente para a grande maioria dos tipos RefCell<T> (mutabilidade interna não atômica)
T: Enviar + Sincronizar Tipos que podem ser compartilhados e passados com segurança entre threads Arc<T>, Mutex<T> Ponteiros brutos *const T, *mut T

(4) Referência rápida para a seleção de métodos de comunicação entre threads

Método de comunicação Tipo Direção do fluxo de dados Bloqueio necessário Cenários aplicáveis
Canal mpsc::channel Unidirecional (Remetente → Destinatário) Não Padrão produtor-consumidor
Canal (vários produtores) mpsc::channel + tx.clone() Multi→Único Não Resultados agregados em multithread
Status de compartilhamento Arc<Mutex<T>> Bidirecional Sim (bloqueio de exclusão mútua) Acesso multithread de leitura/gravação aos mesmos dados
Status de compartilhamento (Leitura/Gravação) Arc<RwLock<T>> Bidirecional Sim (bloqueio de leitura/gravação) Cenário com muitas leituras e poucas gravações
Operações atômicas AtomicUsize et al. Bidirecional Não (no nível do hardware) Contadores/sinalizadores simples
Barreira Barrier Ponto de sincronização Não Ponto de convergência multithread

O Rust não depende de verificações em tempo de execução para garantir a segurança entre threads; em vez disso, ele realiza verificações em tempo de compilação usando as duas características marcadoras Send e Sync. Se um tipo não for Send, a tentativa de passá-lo para outra thread resultará em um erro em tempo de compilação.


4. Exemplos de programação concorrente

▶ Exemplo 1: thread::spawn + join — Criando e aguardando uma thread (Dificuldade ⭐)

RUST
// ============================================
// Demo:thread::spawn Create a Thread、join The Wait Is Over
// Simulation: Several chefs are preparing different dishes at the same time in the kitchen.
// ============================================

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

fn main() {
    println!("=== Kitchen Renovation Begins ===");

    // --- 1. Create three threads to perform different tasks ---
    let chef1 = thread::spawn(|| {
        for i in 1..=3 {
            println!("[Chef A] Chopping vegetables... Cut #{}", i);
            thread::sleep(Duration::from_millis(50));
        }
        "A Finished chopping the vegetables"
    });

    let chef2 = thread::spawn(|| {
        for i in 1..=3 {
            println!("[Chef B] Stir-frying... Step #{}", i);
            thread::sleep(Duration::from_millis(40));
        }
        "B Finished cooking the stir-fry"
    });

    // The main thread is also running
    for i in 1..=3 {
        println!("[Head Chef] Plating in progress... Item #{}", i);
        thread::sleep(Duration::from_millis(60));
    }

    // --- 2. join Wait for all threads to finish and retrieve the return values ---
    let result1 = chef1.join().unwrap();
    let result2 = chef2.join().unwrap();

    println!("\n=== Kitchen Shutdown ===");
    println!("Chef A: {}", result1);
    println!("Chef B: {}", result2);
    println!("All work has been completed!");
}

Resultado:

TEXT 📖 Somente leitura
=== Kitchen Renovation Begins ===
[Chef A] Chopping vegetables... Cut #1
[Chef B] Stir-frying... Step #1
[Head Chef] Plating in progress... Item #1
[Chef A] Chopping vegetables... Cut #2
[Chef B] Stir-frying... Step #2
[Head Chef] Plating in progress... Item #2
[Chef A] Chopping vegetables... Cut #3
[Chef B] Stir-frying... Step #3
[Head Chef] Plating in progress... Item #3

=== Kitchen Shutdown ===
Chef A: A Finished chopping the vegetables
Chef B: B Finished cooking the stir-fry
All work has been completed!

thread::spawn Aceita um closure e o executa em uma nova thread do sistema operacional. join() Bloqueia a thread atual até que a thread de destino seja concluída e retorna Result<T> — se a thread entrar em pânico, join() retorna Err. A ordem em que as threads são executadas é determinada pelo agendador do sistema operacional e pode variar de execução para execução.


▶ Exemplo 2: mpsc::channel — Mensagens (Dificuldade ⭐⭐)

RUST
// ============================================
// Demo: mpsc::channel Many-Producer, Single-Consumer Messaging
// Simulation: Multiple editors send modification requests to the document server
// ============================================

use std::thread;
use std::sync::mpsc;
use std::time::Duration;

#[derive(Debug)]
enum EditAction {
    Insert { user: String, text: String },
    Delete { user: String, line: u32 },
    Format { user: String, style: String },
}

fn main() {
    println!("=== Collaborative Document Editor ===");

    // Create a Channel:Sender Can be cloned,Receiver It is the only one
    let (tx, rx) = mpsc::channel::<EditAction>();

    // --- 1. Start the receive thread (Document Server) ---
    let receiver = thread::spawn(move || {
        for action in rx {
            match &action {
                EditAction::Insert { user, text } => {
                    println!("[Server] {} Inserted: {}", user, text);
                }
                EditAction::Delete { user, line } => {
                    println!("[Server] {} Deleted line {}", user, line);
                }
                EditAction::Format { user, style } => {
                    println!("[Server] {} Formatting has been applied: {}", user, style);
                }
            }
            // Simulated Processing Time
            thread::sleep(Duration::from_millis(20));
        }
        println!("[Server] Passage Closed,Unsubscribe");
    });

    // --- 2. Create the first editor thread (Alice) ---
    let tx1 = tx.clone();
    let editor1 = thread::spawn(move || {
        tx1.send(EditAction::Insert {
            user: "Alice".to_string(),
            text: "Chapter 1: Rust Introduction".to_string(),
        }).unwrap();
        thread::sleep(Duration::from_millis(10));
        tx1.send(EditAction::Format {
            user: "Alice".to_string(),
            style: "Bold the title".to_string(),
        }).unwrap();
    });

    // --- 3. Create the second editor thread (Bob) ---
    let tx2 = tx.clone();
    let editor2 = thread::spawn(move || {
        tx2.send(EditAction::Insert {
            user: "Bob".to_string(),
            text: "Rust is a systems programming language".to_string(),
        }).unwrap();
        thread::sleep(Duration::from_millis(10));
        tx2.send(EditAction::Delete {
            user: "Bob".to_string(),
            line: 1,
        }).unwrap();
    });

    // --- 4. The main thread also sends a message ---
    tx.send(EditAction::Insert {
        user: "System".to_string(),
        text: "Auto-Save Documents".to_string(),
    }).unwrap();

    // Wait for the editor thread to finish
    editor1.join().unwrap();
    editor2.join().unwrap();

    // Close the sender - After all Senders are dropped, the Receiver for loop will end automatically
    // tx is dropped here (because tx is the last sender on the main thread)

    println!("All editors have completed their work");
    receiver.join().unwrap();
    println!("=== End of editing session ===");
}
// Note: tx is automatically dropped when leaving the scope, channel closes
// If you need to explicitly turn it off,Can be used drop(tx)

Resultado:

TEXT 📖 Somente leitura
=== Collaborative Document Editor ===
[Server] System Inserted: Auto-Save Documents
[Server] Alice Inserted: Chapter 1: Rust Introduction
[Server] Bob Inserted: Rust is a systems programming language
[Server] Alice Formatting has been applied: Bold the title
[Server] Bob Deleted line 1
All editors have completed their work
[Server] Passage Closed,Unsubscribe
=== End of editing session ===

mpsc significa “Múltiplos produtores, único consumidor”. Sender pode ser copiado para várias threads por meio de clone, mas só pode haver um Receiver. Quando todos os Sender são descartados, o canal se fecha automaticamente e o iterador de Receiver é encerrado. send retorna Result — se o lado receptor tiver sido fechado, retorna Err.


▶ Exemplo 3: Arc<Mutex<T>> — Acesso seguro entre threads a um estado compartilhado (Dificuldade ⭐⭐⭐)

RUST
// ============================================
// Demo: Arc<Mutex<T>> Safely Sharing Data Among Multiple Threads
// Simulation: Multiple workers modifying a shared counter simultaneously
// ============================================

use std::thread;
use std::sync::{Arc, Mutex};
use std::time::Duration;

fn main() {
    println!("=== Shared Counter Demo ===");

    // Use Arc<Mutex<i32>> to wrap shared data
    let counter = Arc::new(Mutex::new(0i32));
    let mut handles = vec![];

    // --- Start 5 threads,Each thread increments the counter by 10 ---
    for id in 0..5 {
        let counter_clone = Arc::clone(&counter);
        let handle = thread::spawn(move || {
            for _ in 0..10 {
                // lock() Acquire a mutex lock——If the lock is held by another thread,The current thread will block while waiting
                let mut num = counter_clone.lock().unwrap();
                *num += 1;
                println!("[Workers{}] Current Count: {}", id, *num);
                // Locks are automatically released when they go out of scope
            }
            println!("[Workers{}] Work Completed", id);
        });
        handles.push(handle);
    }

    // --- Wait for all threads to finish ---
    for handle in handles {
        handle.join().unwrap();
    }

    // --- Read the final results ---
    let final_count = counter.lock().unwrap();
    println!("\n=== Final Results ===");
    println!("Final counter value: {}", *final_count);
    println!("Expected value: {} (5 threads x 10 times)", 5 * 10);
}

Resultado:

TEXT 📖 Somente leitura
=== Shared Counter Demo ===
[Workers0] Current Count: 1
[Workers0] Current Count: 2
[Workers1] Current Count: 3
[Workers1] Current Count: 4
[Workers0] Current Count: 5
... (The intermediate output varies depending on thread scheduling)
[Workers4] Current Count: 50
[Workers4] Work Completed

=== Final Results ===
Final counter value: 50
Expected value: 50 (5 threads x 10 times)

Mutex<T> oferece exclusão mútua — apenas uma thread pode acessar os dados internos por vez. lock() retorna MutexGuard<T>, que implementa Deref e Drop: o bloqueio é liberado automaticamente ao sair do escopo. Arc<T> (Contagem de Referências Atômica) é a versão multithread de Rc<T>, utilizando operações atômicas para garantir a segurança entre threads na contagem de referências. Arc::clone incrementa a contagem de referências sem copiar dados.


▶ Exemplo 4: Colaboração entre threads — O padrão produtor-consumidor (Dificuldade ⭐⭐⭐)

RUST
// ============================================
// Producer-Consumer: Multiple producers + Single consumer
// ============================================

use std::sync::mpsc;
use std::thread;
use std::time::Duration;

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

    thread::spawn(move || {
        let items = vec!["Apple", "Banana", "Orange"];
        for item in items {
            tx.send(format!("Producer1: {}", item)).unwrap();
            thread::sleep(Duration::from_millis(100));
        }
        println!("Producer1 Done");
    });

    thread::spawn(move || {
        let items = vec!["Watermelon", "Grapes"];
        for item in items {
            tx2.send(format!("Producer2: {}", item)).unwrap();
            thread::sleep(Duration::from_millis(150));
        }
        println!("Producer2 Done");
    });

    println!("=== Consumer Receipt ===");
    for msg in rx {
        println!("  Received: {}", msg);
    }
    println!("All producers have finished, consumer section ended");
}

Saída (a ordem pode variar):

TEXT 📖 Somente leitura
=== Consumer Receipt ===
  Received: Producer1: Apple
  Received: Producer2: Watermelon
  Received: Producer1: Banana
  Received: Producer1: Orange
  Received: Producer2: Grapes
Producer1 Done
Producer2 Done
All producers have finished, consumer section ended

tx.clone() Cria vários emissores para implementar um padrão multiprodutor. Quando todos os emissores (tx e tx2) são removidos, o iterador rx é encerrado automaticamente — não há necessidade de enviar manualmente um sinal de “fim”.


▶ Exemplo 5: Exercício abrangente — Processamento paralelo de dados (Dificuldade ⭐⭐⭐)

RUST
// ============================================
// Parallel Computing: Multithreaded Sharded Summation
// ============================================

use std::sync::{Arc, Mutex};
use std::thread;

fn parallel_sum(data: &[i64], num_threads: usize) -> i64 {
    let chunk_size = (data.len() + num_threads - 1) / num_threads;
    let result = Arc::new(Mutex::new(0i64));
    let mut handles = Vec::new();

    for i in 0..num_threads {
        let chunk = data[i * chunk_size..(i * chunk_size + chunk_size).min(data.len())].to_vec();
        let result = Arc::clone(&result);
        handles.push(thread::spawn(move || {
            let partial: i64 = chunk.iter().sum();
            *result.lock().unwrap() += partial;
            partial
        }));
    }

    let mut partials = Vec::new();
    for handle in handles {
        partials.push(handle.join().unwrap());
    }

    println!("The various thread sections and: {:?}", partials);
    *result.lock().unwrap()
}

fn main() {
    let data: Vec<i64> = (1..=1000).collect();
    let sequential_sum: i64 = data.iter().sum();

    println!("=== Serial Summation ===");
    println!("1 to 1000 sum: {}", sequential_sum);

    println!("\n=== Parallel Summation (4 Thread) ===");
    let parallel_result = parallel_sum(&data, 4);
    println!("Parallel Summation Results: {}", parallel_result);
    assert_eq!(sequential_sum, parallel_result);

    let data2: Vec<i64> = (1..=10_000_000).collect();
    let start = std::time::Instant::now();
    let _ = data2.iter().sum::<i64>();
    let seq_time = start.elapsed();

    let start = std::time::Instant::now();
    let _ = parallel_sum(&data2, 8);
    let par_time = start.elapsed();

    println!("\n=== 10M Data Performance Comparison ===");
    println!("Serial: {:?}", seq_time);
    println!("Parallel (8Thread): {:?}", par_time);
}

Resultado:

TEXT 📖 Somente leitura
=== Serial Summation ===
1 to 1000 sum: 500500

=== Parallel Summation (4 Thread) ===
The various thread sections and: [78126, 218874, 109374, 94126]
Parallel Summation Results: 500500

=== 10M Data Performance Comparison ===
Serial: [Time]
Parallel (8Thread): [Time]

parallel_sum Divida os dados; cada thread calcula uma soma parcial, que é então acumulada no resultado total por meio de Arc<Mutex<i64>>. chunk_size Arredonde para cima para garantir que todos os dados sejam considerados. assert_eq! Verifique se o resultado paralelo corresponde ao resultado serial. Para grandes conjuntos de dados, o uso de multithreading pode melhorar significativamente o desempenho.


❓ Perguntas Frequentes

P: thread::spawn Qual é a relação entre as threads criadas e a thread principal? R: Todas as threads são executadas em paralelo; quando a thread principal é encerrada, todo o programa termina. spawn cria threads independentes do sistema operacional. Se a thread principal for encerrada antes que uma thread filha termine, a thread filha será encerrada à força. Portanto, geralmente é necessário usar join() para aguardar a conclusão de todas as threads filhas. Um panic em uma thread filha não afetará as outras threads.

P: As funções mpsc::channel, send e recv bloqueiam? R: A função send normalmente não bloqueia (ela retorna imediatamente quando há espaço no buffer), enquanto a função recv bloqueia até que uma mensagem seja recebida. mpsc::channel cria um canal limitado (que, na verdade, é um buffer infinito). send retorna Err quando o lado receptor é fechado. recv bloqueia a thread quando não há mensagens disponíveis e retorna Err quando o canal é fechado. try_recv oferece uma versão não bloqueante.

P: O Mutex<T> e o Arc<Mutex<T>> precisam ser usados juntos? R: Não necessariamente. Se o Mutex<T> for usado em uma única thread, o Arc não é necessário. No entanto, em um cenário multithread, cada thread precisa da propriedade ou de uma referência a um Mutex— o Arc fornece propriedade compartilhada (contagem de referências), permitindo que múltiplas threads mantenham um identificador para o mesmo Mutex. Se você usar Rc<Mutex<T>>, o compilador reportará um erro (Rc não é Send).

P: O uso de Mutex pode causar um impasse? R: O Rust não impede impasses; você deve evitá-los em seu código. Cenários comuns de impasse: duas threads, cada uma detendo um bloqueio, aguardam o bloqueio da outra. A biblioteca padrão do Rust não oferece detecção automática de impasses — isso é de responsabilidade do programador. Estratégias para reduzir impasses incluem: manter uma ordem consistente de bloqueio, usar try_lock em vez de lock e minimizar o escopo dos bloqueios tanto quanto possível.

P: Qual é a diferença entre Send e Sync? R: Send significa “a propriedade pode ser transferida entre threads”, e Sync significa “as referências podem ser compartilhadas entre threads”. Ambas são características inseguras (implementadas automaticamente; não é necessária nenhuma implementação manual). Rc<T> não é Send (contagem de referências não atômica), e RefCell<T> não é Sync (as verificações de empréstimo em tempo de execução não são seguras entre threads). A grande maioria dos tipos da biblioteca padrão é tanto Send quanto Sync.


📖 Resumo


📝 Exercícios

  1. Dificuldade ⭐: Escreva um programa que crie três threads para calcular as somas de 1..=10, 11..=20 e 21..=30, respectivamente. A thread principal usa join para aguardar a conclusão de todas as threads; em seguida, agrega as três somas parciais e exibe o resultado final.
  2. Dificuldade ⭐⭐: Use mpsc::channel para implementar um “distribuidor de tarefas”. Crie 1 thread produtor (que gera 10 tarefas numeradas de 1 a 10) e 2 threads consumidores (cada consumidor recebe um número de tarefa do canal e exibe “[Consumidor X] processando tarefa nº N”). Certifique-se de que todas as tarefas sejam processadas.
  3. Dificuldade ⭐⭐⭐: Implemente um sistema de “conta bancária compartilhada”. Use Arc<Mutex<f64>> como saldo compartilhado. Crie 4 threads para simular operações de depósito (cada thread deposita um valor aleatório entre 10 e 100 yuan). A thread principal aguarda a conclusão de todas as threads de depósito antes de ler o saldo final. Requisito adicional: Imprima a variação do saldo antes e depois de cada depósito para verificar se não há condição de corrida (saldo final = saldo inicial + soma de todos os depósitos).
Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%