2024-10-11
008 - Concurrency in Rust for Python Developers
Learning Rust as a Pythonista: Concurrency in Rust
This is part 1 of concurrency. We focus on threads, channels, and shared state. The async runtime model (tokio) will be covered in the next lesson.
Why this matters for Python developers
- Python concurrency often starts with
threading,concurrent.futures, ormultiprocessing. - Due to the GIL, Python threads are usually best for I/O-bound tasks.
- Rust threads run in parallel on multiple cores and are checked for memory safety at compile time.
Learning goals
By the end of this lesson, you should be able to:
- Spawn and join Rust threads.
- Send messages between threads with channels.
- Share mutable state safely with
Arc<Mutex<T>>.
Concepts in 5 minutes
std::thread::spawnstarts a new OS thread.join()waits for a thread to finish.std::sync::mpscprovides channels for message passing.Arc<Mutex<T>>enables shared mutable state with synchronization.
Python baseline snippets
from concurrent.futures import ThreadPoolExecutor
def work(x):
return x * x
with ThreadPoolExecutor() as pool:
print(list(pool.map(work, [1, 2, 3])))
# For CPU-bound tasks in Python, processes are usually preferred.
from concurrent.futures import ProcessPoolExecutor
Rust equivalent snippets
1) Basic thread spawn
use std::thread;
fn main() {
let handle = thread::spawn(|| {
println!("hello from worker thread");
});
handle.join().unwrap();
}
2) Channels for message passing
use std::sync::mpsc;
use std::thread;
fn main() {
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
tx.send(String::from("done")).unwrap();
});
println!("received: {}", rx.recv().unwrap());
}
3) Shared mutable state with Arc<Mutex<T>>
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
let counter = Arc::new(Mutex::new(0));
let mut handles = Vec::new();
for _ in 0..5 {
let counter = Arc::clone(&counter);
handles.push(thread::spawn(move || {
let mut value = counter.lock().unwrap();
*value += 1;
}));
}
for h in handles {
h.join().unwrap();
}
println!("counter = {}", *counter.lock().unwrap());
}
One runnable end-to-end example
use std::sync::{mpsc, Arc, Mutex};
use std::thread;
fn main() {
// 1) Spawn workers and send results over a channel
let (tx, rx) = mpsc::channel();
let inputs = vec![1, 2, 3, 4];
for n in inputs {
let tx = tx.clone();
thread::spawn(move || {
let result = n * n;
tx.send(result).unwrap();
});
}
drop(tx);
let mut total = 0;
for value in rx {
total += value;
}
println!("sum of squares = {}", total);
// 2) Shared state safely with Arc<Mutex<T>>
let counter = Arc::new(Mutex::new(0));
let mut handles = Vec::new();
for _ in 0..10 {
let counter = Arc::clone(&counter);
handles.push(thread::spawn(move || {
let mut c = counter.lock().unwrap();
*c += 1;
}));
}
for handle in handles {
handle.join().unwrap();
}
println!("final counter = {}", *counter.lock().unwrap());
}
Common mistakes
- Forgetting
movewhen a spawned thread needs ownership. - Holding a
Mutexlock longer than necessary. - Mixing shared mutable state everywhere instead of preferring channels.
Quick practice
- Modify the channel example to send
(input, output)tuples. - Replace the shared counter with a shared
Vec<i32>and push values safely.
Recap
Rust threads provide true parallelism and compile-time memory safety. Start with channels for communication, then use Arc<Mutex<T>> only when shared mutable state is necessary.
In the next lesson, we’ll cover async concurrency in Rust with tokio.
Join the Journey Ahead!
If you're eager to continue this learning journey and stay updated with the latest insights, consider subscribing. By joining our mailing list, you'll receive notifications about new articles, tips, and resources to help you seamlessly pick up Rust by leveraging your Python skills.
Other articles in the series
- 000 - Learning Rust as a Pythonista: A Suggested Path
- 001 - Your First Rust Program
- 002 - Basic Syntax and Structure
- 003 - Ownership, Borrowing, and Lifetimes
- 004 - Error Handling
- 005 - Structs and Enums
- 006 - Iterators and Closures
- 007 - Traits vs Duck Typing and Protocols
- 009 - Async Concurrency with Tokio
- 010 - Pattern Matching in Rust
- 011 - Macros in Rust