Rust

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

6 min de leitura

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

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.

<h2>Criando Threads em Rust</h2>

<p>A criação de threads em Rust é simples e segura graças ao sistema de tipos da linguagem. A função <code>std::thread::spawn()</code> permite iniciar uma nova thread, retornando um identificador (<code>JoinHandle</code>) 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.</p>

<pre><code class="language-rust">use std::thread;

use std::time::Duration;

fn main() {

let handle = thread::spawn(|| {

for i in 1..=5 {

println!(&quot;Thread: {}&quot;, i);

thread::sleep(Duration::from_millis(100));

}

});

for i in 1..=3 {

println!(&quot;Main: {}&quot;, i);

thread::sleep(Duration::from_millis(150));

}

handle.join().unwrap(); // Aguarda conclusão

println!(&quot;Threads finalizadas!&quot;);

}</code></pre>

<p>Neste exemplo, a closure captura o ambiente implicitamente. O <code>join()</code> é essencial — ele bloqueia a thread principal até que a spawned thread termine. Sem ele, o programa poderia encerrar antes da thread completar seu trabalho.</p>

<h2>Sincronização com Mutex e Arc</h2>

<p>Quando múltiplas threads precisam acessar dados compartilhados, você deve usar sincronização. O <code>Mutex&lt;T&gt;</code> protege dados contra acesso simultâneo, enquanto <code>Arc&lt;T&gt;</code> (Atomic Reference Counting) permite propriedade compartilhada entre threads. Essa combinação, <code>Arc&lt;Mutex&lt;T&gt;&gt;</code>, é o padrão fundamental para dados mutáveis compartilhados.</p>

<pre><code class="language-rust">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(&amp;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!(&quot;Resultado: {}&quot;, *counter.lock().unwrap());

}</code></pre>

<p>O <code>Arc::clone()</code> incrementa o contador de referências sem clonar os dados reais. O <code>lock()</code> tenta adquirir um lock exclusivo; se outra thread já possui, bloqueia até liberação. O <code>unwrap()</code> 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 <code>Resultado: 5</code>, demonstrando sincronização correta.</p>

<h2>Channels para Comunicação entre Threads</h2>

<p>Para padrões produtor-consumidor, <code>mpsc::channel()</code> (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.</p>

<pre><code class="language-rust">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!(&quot;Recebido: {}&quot;, received);

}

handle.join().unwrap();

}</code></pre>

<p>Aqui, o sender (<code>tx</code>) é 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 <code>unwrap()</code> no <code>send()</code> panic se o receiver foi dropado; versões robustas verificam <code>Result</code>.</p>

<h2>Padrões Avançados e Boas Práticas</h2>

<p>Para aplicações complexas, considere thread pools com bibliotecas como <code>rayon</code> ou <code>tokio</code>. O <code>rayon</code> simplifica paralelismo de dados (map-reduce), enquanto <code>tokio</code> 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.</p>

<pre><code class="language-rust">use rayon::prelude::*;

fn main() {

let data = vec![1, 2, 3, 4, 5, 6, 7, 8];

let result: Vec&lt;i32&gt; = data

.par_iter()

.map(|x| x * x)

.collect();

println!(&quot;{:?}&quot;, result); // [1, 4, 9, 16, 25, 36, 49, 64]

}</code></pre>

<p>Este exemplo com <code>rayon</code> 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.</p>

<h2>Conclusão</h2>

<p>Rust torna concorrência segura através de três mecanismos principais: <strong>propriedade e ciclo de vida</strong> previnem data races em compilação; <strong>Mutex + Arc</strong> sincronizam dados compartilhados com segurança; <strong>channels</strong> 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 <code>spawn()</code>, <code>join()</code>, <code>Mutex</code>, <code>Arc</code> e <code>mpsc::channel()</code> e você terá fundação sólida para qualquer aplicação concorrente.</p>

<h2>Referências</h2>

<ul>

<li><a href="https://doc.rust-lang.org/book/ch16-00-concurrency.html" target="_blank" rel="noopener noreferrer">The Rust Book - Fearless Concurrency</a></li>

<li><a href="https://doc.rust-lang.org/std/sync/" target="_blank" rel="noopener noreferrer">Rust std::sync documentation</a></li>

<li><a href="https://docs.rs/rayon/" target="_blank" rel="noopener noreferrer">Rayon: Data Parallelism</a></li>

<li><a href="https://tokio.rs/" target="_blank" rel="noopener noreferrer">Tokio async runtime</a></li>

<li><a href="https://www.oreilly.com/library/view/programming-rust-2nd/9781492052586/" target="_blank" rel="noopener noreferrer">Programming Rust, 2nd Edition - Jim Blandy, Jason Orendorff</a></li>

</ul>

Comentários

Mais em Rust

Como Usar Borrowing e Referências em Rust: & e &mut na Prática em Produção
Como Usar Borrowing e Referências em Rust: & e &mut na Prática em Produção

Entendendo o Sistema de Ownership e Borrowing O Rust resolve o problema de ge...

O que Todo Dev Deve Saber sobre Streams Assíncronos e Concorrência Avançada com Tokio
O que Todo Dev Deve Saber sobre Streams Assíncronos e Concorrência Avançada com Tokio

Introdução ao Tokio: Fundações de Assincronismo em Rust Tokio é o runtime ass...

O que Todo Dev Deve Saber sobre WebAssembly com Rust: Compilando para o Navegador com wasm-pack
O que Todo Dev Deve Saber sobre WebAssembly com Rust: Compilando para o Navegador com wasm-pack

Introdução ao WebAssembly com Rust WebAssembly (WASM) é um formato binário qu...