Parallel Programming
Why Parallel?
Hot topic. Meh
CPUs/memory aren't getting faster, just more parallel
Big Data / Machine Learning etc
Some things are naturally concurrent: user input, agent-based systems, etc
Note that parallel ⇒ concurrent but not vice-versa
Why Not Parallel?
Generally harder to get right
Only gives linear speedups
Rust's Parallel Story
Eliminates shared-memory errors
Eliminates data races
No significant overhead
Parallel systems are still hard: partitioning, deadlocks, termination, etc
Approaches To Parallelism
Background thread
Worker pool
Pipeline
Data parallel
"Sea of synchronized objects"
Approaches To Parallel Comms / Sync
Channels
Mutexes, semaphores, etc
Shared memory, maybe with CPU-atomic operations
Fork/Join, Shared Memory
The "old-school" approach
Rust's error handling is nice here.
Getting Memory To Threads
Can use
move
closure to move data into threadCan use
Arc
to share data across threads
Rayon
Uses "map-reduce" paradigm with worker pool
The demo here is not too good a match to Rayon's use case
(Mandelbrot)
- Not covering this today
Channels
Nice way to get values sent for long-running threads
mpsc
: allows building DAGs of channelsNeed
mpmc
to distribute messages among threadsData is actually moved or shared across channels; efficient
Pipeline
Easy-to-get-right thing; used in CPUs a lot
A bit tricky to set up
Only gives parallelism with multiple pipeline operations
Send + Sync
Marker traits for thread safety
Automatically maintained (for the most part) by the compiler
Send
: Safe to move value to another threadSync
: Safe to share a non-mut reference to value with another threadSend
andSync
are auto-derived for structs / enums
A Pipeline Iterator
- Nice book example of iterator construction for pipelining
Synchronization
Mutex<T>
: Wraps a value in a mutex lock- For example
mutex = Mutex::new(0_usize)
- Only one thread may complete
mutex.lock()
at a time - That thread gets a mutable reference to the wrapped data
- Other threads are blocked until first releases lock
- Returns a
Result
because mutex poisoning - For example
*mutex.lock().unwrap() = 5
- For example
RwLock<T>
: A mutex that will give out many shared immutable refs or one mutable ref at a timeCondvar
: Just don'tAtomicUsize
et al: Ensures that read-modify-write operations happen as an atomic thingProvides methods like
.fetch_add(1, Ordering::SeqCst)
The "memory ordering" should probably just always be set to that
Not as great as they sound
Book Misc
MPMC (but note section title is wrong)
lazy_static!