Skip to content
DocsRust LearningexpertConcurrency
Chapter 14 of 19·expert·10 min read

Concurrency

Đồng Thời

Threads, channels, and shared state

Hover or tap any paragraph to see Vietnamese translation

Concurrency in Rust

Rust provides safe concurrency through its type system. Rust's "fearless concurrency" slogan means the compiler catches data races and thread-safety issues at compile time rather than letting them surface at runtime.

Creating Threads with std::thread::spawn

The thread::spawn function creates a new thread and takes a closure to execute. It returns a JoinHandle you can use to wait for the thread to finish. If you do not call join(), the main thread may exit before the spawned thread completes.

thread_spawn.rs
1use std::thread;2use std::time::Duration;34fn main() {5    // Spawn a new thread6    let handle = thread::spawn(|| {7        for i in 1..=5 {8            println!("spawned thread: {}", i);9            thread::sleep(Duration::from_millis(1));10        }11    });12

Moving Data into Threads (move closures)

By default, closures borrow values from their surrounding environment. But since a spawned thread might outlive the thread that created it, Rust requires you to move data into the thread rather than borrow it. The move keyword forces the closure to take ownership of all captured values.

move_closure.rs
1use std::thread;23fn main() {4    let data = vec![1, 2, 3];56    // ERROR: cannot borrow data here because the thread7    // might outlive the current function8    // let handle = thread::spawn(|| println!("{:?}", data));910    // CORRECT: move transfers ownership into the thread11    let handle = thread::spawn(move || {12        println!("Thread has: {:?}", data);

Message Passing with Channels

Rust follows the philosophy of "communicate by sending messages rather than sharing memory" for inter-thread communication. The mpsc::channel() function creates a channel with one sender and one receiver. mpsc stands for "multiple producer, single consumer."

channels.rs
1use std::sync::mpsc;2use std::thread;3use std::time::Duration;45fn main() {6    // tx = transmitter (sender), rx = receiver7    let (tx, rx) = mpsc::channel();89    thread::spawn(move || {10        let messages = vec!["hello", "from", "the", "thread"];11        for msg in messages {12            tx.send(msg).unwrap();

Multiple Producers

The "multiple producer" part of mpsc means you can have multiple senders sending to the same receiver. You use tx.clone() to create additional senders. All clones of the sender transmit to the same receiver.

multiple_producers.rs
1use std::sync::mpsc;2use std::thread;3use std::time::Duration;45fn main() {6    let (tx, rx) = mpsc::channel();78    // Clone sender for each producer thread9    let tx1 = tx.clone();10    let tx2 = tx.clone();1112    thread::spawn(move || {

Shared State with Mutex<T>

A Mutex (Mutual Exclusion) ensures that only one thread can access the data at a time. To access the data, a thread must request the lock. When done, the lock is automatically released when the MutexGuard goes out of scope.

mutex_basics.rs
1use std::sync::Mutex;23fn main() {4    let m = Mutex::new(5);56    {7        // lock() blocks until the lock is acquired8        // Returns a MutexGuard (smart pointer to the data)9        let mut num = m.lock().unwrap();10        *num = 6;11        // Lock released here when MutexGuard drops12    }

The Arc<Mutex<T>> Pattern

To share a Mutex across multiple threads, you need to wrap it in Arc<T>. Arc provides thread-safe multiple ownership, while Mutex provides safe mutable access. This combination is the most common pattern for shared mutable state across threads.

arc_mutex_pattern.rs
1use std::sync::{Arc, Mutex};2use std::thread;34// Example: accumulate results from worker threads5fn parallel_sum(numbers: Vec<i32>) -> i32 {6    let result = Arc::new(Mutex::new(0));7    let mut handles = vec![];8    let chunk_size = numbers.len() / 4;910    for chunk in numbers.chunks(chunk_size) {11        let result = Arc::clone(&result);12        let chunk = chunk.to_vec();
Tip
Arc<Mutex<T>> is the multi-threaded counterpart of Rc<RefCell<T>>. Use Rc<RefCell<T>> for single-threaded and Arc<Mutex<T>> for multi-threaded scenarios.

Send and Sync Traits

Rust has two special marker traits for concurrency safety. Send means ownership of that type can be transferred between threads. Sync means references to that type can be shared between threads. Most primitive types are both Send and Sync.

send_sync_traits.rs
1use std::sync::{Arc, Mutex};23// Send: ownership can be moved to another thread4// Most types are automatically Send5// Exceptions: Rc<T>, raw pointers, RefCell<T> in cross-thread context67// Sync: &T is safe to send to another thread8// A type T is Sync if &T is Send9// Mutex<T> is Sync (controlled access)10// Cell<T> and RefCell<T> are NOT Sync1112// Example: a custom type that is Send + Sync

Deadlock Prevention

A deadlock occurs when two threads are each waiting for the other to release a lock, causing both to block forever. Rust does not automatically prevent deadlocks, but there are several strategies to avoid them.

deadlock_prevention.rs
1use std::sync::{Arc, Mutex};2use std::thread;34// DEADLOCK scenario (avoid this pattern):5// Thread 1: locks A, then tries to lock B6// Thread 2: locks B, then tries to lock A78// STRATEGY 1: Always acquire locks in the same order9fn safe_transfer(10    account_a: &Arc<Mutex<f64>>,11    account_b: &Arc<Mutex<f64>>,12    amount: f64,

Rayon for Data Parallelism

Rayon is a third-party crate providing easy data parallelism. By replacing regular iterators with parallel iterators, you can leverage all CPU cores without manual thread management. Rayon automatically splits work among threads in a thread pool.

rayon_parallel.rs
1// Cargo.toml:2// [dependencies]3// rayon = "1"45use rayon::prelude::*;67fn main() {8    let numbers: Vec<i32> = (1..=1_000_000).collect();910    // Sequential sum11    let seq_sum: i32 = numbers.iter().sum();12
Warning
Rayon is not always faster than sequential code. With small datasets or very fast operations, the thread coordination overhead may exceed the parallelism benefit.

Key Takeaways

Điểm Chính

  • Rust prevents data races at compile timeRust ngăn chặn data race tại thời điểm biên dịch
  • Use channels (mpsc) for message-passing between threadsSử dụng channel (mpsc) để truyền thông điệp giữa các luồng
  • Mutex provides safe shared mutable state with lockingMutex cung cấp trạng thái chia sẻ có thể thay đổi an toàn với khóa
  • Arc enables thread-safe reference counting for shared ownershipArc cho phép đếm tham chiếu an toàn giữa các luồng

Practice

Test your understanding of this chapter

Quiz

What does 'mpsc' stand for in std::sync::mpsc?

"mpsc" trong std::sync::mpsc là viết tắt của gì?

Quiz

Why is Rc<T> not allowed to be sent across thread boundaries while Arc<T> is?

Tại sao Rc<T> không được phép gửi qua ranh giới luồng trong khi Arc<T> thì được?

True or False

Rust's type system prevents deadlocks at compile time, just as it prevents data races.

Hệ thống kiểu của Rust ngăn chặn deadlock tại compile time, giống như cách nó ngăn chặn data race.

True or False

When using a move closure with thread::spawn, all captured variables are moved into the closure and are no longer accessible in the original thread.

Khi sử dụng move closure với thread::spawn, tất cả biến được nắm bắt đều bị di chuyển vào closure và không còn truy cập được trong luồng gốc.

Code Challenge

Acquire a Mutex lock to safely modify shared data across threads

Lấy khóa Mutex để sửa đổi an toàn dữ liệu chia sẻ giữa các luồng

let counter = Arc::new(Mutex::new(0));
let clone = Arc::clone(&counter);
thread::spawn(move || {
    let mut num = clone.().unwrap();
    *num += 1;
});

Chapter Complete!

Great job! Keep the momentum going.

Your progress0 of 19 chapters read
← → to navigate chapters
Built: 4/8/2026, 12:01:11 PM