Master

Concurrency

Build safe concurrency with threads, channels, and send/sync.

What you will see

1

About 2 min of reading

Try It

You will build safe concurrency with threads, channels, and shared state protected by a Mutex.

1

I spawn a thread that prints and join it. join waits for the thread to finish before main ends.

use std::thread; fn main() { let handle = thread::spawn(|| { println!("running in a thread"); }); handle.join().unwrap(); println!("main done"); }

Before you run it, say out loud what it will print. Then open the answer.

What it prints
running in a thread main done
2

I send a value over a channel from a thread and receive it in main. Channels move data without shared memory.

use std::sync::mpsc; fn main() { let (tx, rx) = mpsc::channel(); thread::spawn(move || { tx.send("done").unwrap(); }); println!("{}", rx.recv().unwrap()); } use std::thread;

Before you run it, say out loud what it will print. Then open the answer.

What it prints
done
3

I share a mutex-protected counter across threads. Each thread locks, increments, and drops the lock.

use std::sync::Arc; use std::sync::Mutex; use std::thread; fn main() { let data = Arc::new(Mutex::new(0)); let mut handles = vec![]; for _ in 0..3 { let d = Arc::clone(&data); handles.push(thread::spawn(move || { *d.lock().unwrap() += 1; })); } for h in handles { h.join().unwrap(); } println!("{}", *data.lock().unwrap()); }

Before you run it, say out loud what it will print. Then open the answer.

What it prints
3
4

I try to share an Rc across threads. Rc is not Send, so the compiler rejects moving it into a thread.

use std::rc::Rc; use std::thread; fn main() { let data = Rc::new(0); let clone = Rc::clone(&data); thread::spawn(move || { println!("{}", clone); }); }

Before you run it, say out loud what it will print. Then open the answer.

What it prints
COMPILE_ERROR
Rc is not Send; use Arc to share values across threads.
5

I lock a mutex and increment it once. The guard drops at the end of the block, releasing the lock.

use std::sync::Mutex; fn main() { let counter = Mutex::new(0); *counter.lock().unwrap() += 1; println!("{}", *counter.lock().unwrap()); }

Before you run it, say out loud what it will print. Then open the answer.

What it prints
1

Put it in order

Before you write anything, put these lines back in the right order. Click a line to move it up one step. This support step is easier than writing code from scratch, and it is here to make the last example stick.

Data Flow

Threads run in parallel; channels pass data; Arc shares state safely.
1 Spawn threads with spawn
↓
2 Each thread computes
↓
3 Send results over a channel
↓
4 Main thread collects and joins
Why this works
Send and Sync

Two auto traits govern threads:

  • Send: a value can be moved to another thread.
  • Sync: a value can be shared by reference across threads.

fn assert_send<T: Send>() {}

Most types are Send and Sync automatically. Smart pointers like Rc are not Send: use Arc instead.

Why it works: these traits let Rust guarantee thread safety.

Mutex

A Mutex serializes access:

let counter = Mutex::new(0);
*counter.lock().unwrap() += 1;

lock() returns a guard. Holding the guard means no other thread can access the data. unwrap panics if another thread panicked while holding the lock.

Examples

Spawning threads

Parallel work

use std::thread;

let handle = thread::spawn(|| {
    println!("running in a thread");
});

handle.join().unwrap();

thread::spawn starts a new thread. join waits for it to finish. Threads have separate stacks and memory.

Why it works: Rust's ownership makes data sharing across threads safe.

Channels with mpsc

Sending data

use std::sync::mpsc;

let (tx, rx) = mpsc::channel();
thread::spawn(move || {
    tx.send("done").unwrap();
});
println!("{}", rx.recv().unwrap());

mpsc is a multi-producer, single-consumer channel. send passes a value; recv receives it. Both block until the other side is ready.

Why it works: channels move data between threads without shared memory.

Arc for shared state

Safe sharing

use std::sync::Arc;
use std::sync::Mutex;

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

Arc<T> is a thread-safe reference counter. Combined with Mutex, many threads can share and protect state.

Why it works: Arc + Mutex gives safe concurrent mutation.

Words this lesson uses

Each one is defined here on this site. Open the entry for the full definition and the other words it points at.

← Modules and Packages
Previous Lesson
Modules and Packages
Next Lesson
Async Rust
Async Rust →