Rust: Concurrent Programming in Rust

Last updated: 2026-08-26

Concurrency is a program's ability to do multiple things "at the same time"—Rust eliminates data races at compile time through its ownership and type systems, allowing you to write concurrent code that is both efficient and safe.

If a single-threaded program is like "one person working in the kitchen from start to finish," then multithreading is like "multiple chefs working at the same time"—some chopping vegetables, some stir-frying, and some plating the dishes. But when there are too many people in the kitchen, things can easily get chaotic: two people might reach for the same knife at the same time, or one person might grab an ingredient that someone else is currently using. Rust's concurrency model is like a "multi-chef kitchen with rules"—each chef has their own tools, ingredients are passed through dedicated channels, and shared seasonings can only be used by one person at a time.


1. What You'll Learn



2. The Story of a Collaborative Document Editing System

(1) Pain Point: Online documentation without concurrency control

Luna's company is developing an online collaborative document editor. Early versions only allowed one person to edit at a time, leading to constant complaints from team members:

"It would be great if we could start a separate thread for each editor, pass modification requests through a channel, and protect the document content with a mutex..."

(2) Approaches to the Rust Concurrency Model

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);
}

Rust's concurrency solution clearly separates responsibilities: each editor runs in a separate thread, edit requests are passed via mpsc::channel, and document content is protected by Mutex. Arc allows Mutex to be shared across multiple threads. The ownership system ensures that no race conditions occur.



3. Core Concepts

(1) The Rust Concurrency Framework

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) Comparison of Three Concurrency Models

Feature thread::spawn (thread) mpsc::channel (channel) Arc<Mutex<T>> (shared state)
Core Concept Independent Execution Units Message Passing Shared Memory + Locks
Data Transfer Methods move: Transfer ownership via a closure send/recv: Send a message lock: Retrieve an internal value
Use Cases Parallel execution of independent tasks Producer-consumer pattern Multiple threads accessing the same data
Advantages Makes full use of multi-core CPUs Decouples senders and receivers Directly shares any type
Disadvantages Complex inter-thread communication Not suitable for frequent small data transfers Potential deadlocks and performance overhead
Rust Features Ownership prevents dangling pointers The compiler ensures correct usage Prevents data races at compile time

(3) Send and Sync trait

Trait Meaning Automatically Implemented Conditions Non-Automatically Implemented Types
Send Ownership of a type can be transferred across threads Implemented automatically for the vast majority of types Rc<T> (non-atomic reference counting)
Sync References to types can be shared across threads Implemented automatically for the vast majority of types RefCell<T> (non-atomic internal mutability)
T: Send + Sync Types that can be safely shared and passed between threads Arc<T>, Mutex<T> Raw pointers *const T, *mut T

(4) Quick Reference for Selecting Thread Communication Methods

Communication Method Type Data Flow Direction Lock Required Applicable Scenarios
Channel mpsc::channel Unidirectional (Sender→Receiver) No Producer-consumer pattern
Channel (multi-producer) mpsc::channel + tx.clone() Multi→Single No Multithreaded aggregated results
Sharing Status Arc<Mutex<T>> Bidirectional Yes (mutual exclusion lock) Multithreaded read/write access to the same data
Sharing Status (Read/Write) Arc<RwLock<T>> Bidirectional Yes (Read/Write Lock) Read-heavy, write-light scenario
Atomic Operations AtomicUsize et al. Bidirectional No (at the hardware level) Simple counters/flags
Barrier Barrier Synchronization Point No Multithreaded Convergence Point

Rust does not rely on runtime checks to ensure thread safety; instead, it performs compile-time checks using the two marker traits Send and Sync. If a type is not Send, attempting to pass it to another thread will result in a compile-time error.



4. Examples of Concurrent Programming

▶ Example 1: thread::spawn + join—Creating and waiting for a thread (Difficulty ⭐)

Output:

TEXT 📖 Display only
=== Kitchen Renovation Begins ===
[Chef A] Chopping vegetables... Cut #<i>
[Chef B] Stir-frying... Step #<i>
[Head Chef] Plating in progress... Item #<i>

=== Kitchen Shutdown ===
Chef A: <result1>
Chef B: <result2>
All work has been completed!
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!");
}

Output:

TEXT 📖 Display only
=== 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 Accepts a closure and executes it in a new operating system thread. join() Blocks the current thread until the target thread finishes, and returns Result<T>—if the thread panics, join() returns Err. The order in which threads are executed is determined by the operating system scheduler and may vary from run to run.


▶ Example 2: mpsc::channel—Messaging (Difficulty ⭐⭐)

Output:

TEXT 📖 Display only
=== Collaborative Document Editor ===
[Server] <user> Inserted: <text>
[Server] <user> Deleted line <line>
[Server] <user> Formatting has been applied: <style>
[Server] Passage Closed,Unsubscribe
All editors have completed their work
=== End of editing session ===
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)

Output:

TEXT 📖 Display only
=== 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 stands for "Multiple Producer, Single Consumer." Sender can be copied to multiple threads via clone, but there can only be one Receiver. When all Sender are dropped, the channel automatically closes, and the iterator for Receiver terminates. send returns Result—if the receiving end has closed, it returns Err.


▶ Example 3: Arc<Mutex<T>>—Thread-Safe Access to Shared State (Difficulty ⭐⭐⭐)

Output:

TEXT 📖 Display only
=== Shared Counter Demo ===
[Workers<id>] Current Count: <*num>
[Workers<id>] Work Completed

=== Final Results ===
Final counter value: <*final_count>
Expected value: 50 (5 threads x 10 times)
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);
}

Output:

TEXT 📖 Display only
=== 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> provides mutual exclusion—only one thread can access the internal data at a time. lock() returns MutexGuard<T>, which implements Deref and Drop: the lock is automatically released when the scope is exited. Arc<T> (Atomic Reference Counted) is the multithreaded version of Rc<T>, using atomic operations to ensure thread safety for reference counting. Arc::clone increments the reference count without copying data.


▶ Example 4: Inter-thread Collaboration—The Producer-Consumer Pattern (Difficulty ⭐⭐⭐)

Output:

TEXT 📖 Display only
Producer1 Done
Producer2 Done
=== Consumer Receipt ===
  Received: <msg>
All producers have finished, consumer section ended
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");
}

Output:

TEXT 📖 Display only
=== 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() Creates multiple senders to implement a multi-producer pattern. When all senders (tx and tx2) are dropped, the rx iterator automatically terminates—there is no need to manually send an "end" signal.


▶ Example 5: Comprehensive Exercise—Parallel Data Processing (Difficulty ⭐⭐⭐)

Output:

TEXT 📖 Display only
The various thread sections and: <partials>
=== Serial Summation ===
1 to 1000 sum: <sequential_sum>

=== Parallel Summation (4 Thread) ===
Parallel Summation Results: <parallel_result>

=== 10M Data Performance Comparison ===
Serial: <seq_time>
Parallel (8Thread): <par_time>
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);
}

Output:

TEXT 📖 Display only
=== 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 Partition the data; each thread calculates a partial sum, which is then accumulated into the total result via Arc<Mutex<i64>>. chunk_size Round up to ensure that all data is accounted for. assert_eq! Verify that the parallel result matches the serial result. For large datasets, multithreading can significantly improve performance.


❓ FAQ

Q thread::spawn What is the relationship between the threads created and the main thread?
A All threads run in parallel; when the main thread terminates, the entire program ends.
Q Do mpsc::channel's send and recv block?
A send typically does not block (it returns immediately when there is space in the buffer), while recv blocks until a message is received.
Q Do Mutex<T> and Arc<Mutex<T>> have to be used together?
A Not necessarily.
Q Can using Mutex cause a deadlock?
A Rust does not prevent deadlocks; you must avoid them in your code.
Q What is the difference between Send and Sync?
A Send means "ownership can be transferred across threads," and Sync means "references can be shared across threads."

📖 Summary


📝 Exercises

  1. Difficulty ⭐: Write a program that creates three threads to calculate the sums of 1..=10, 11..=20, and 21..=30, respectively. The main thread uses join to wait for all threads to finish, then aggregates the three partial sums and prints the final result.
  2. Difficulty ⭐⭐: Use mpsc::channel to implement a "task dispatcher." Create 1 producer thread (which generates 10 tasks numbered 1–10) and 2 consumer threads (each consumer receives a task number from the channel and prints "[Consumer X] processing task #N"). Ensure that all tasks are processed.
  3. Difficulty ⭐⭐⭐: Implement a "shared bank account" system. Use Arc<Mutex<f64>> as the shared balance. Create 4 threads to simulate deposit operations (each thread deposits a random amount between 10 and 100 yuan). The main thread waits for all deposit threads to finish before reading the final balance. Additional requirement: Print the balance change before and after each deposit to verify that there is no race condition (final balance = initial balance + sum of all deposits).
Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏