Intro to Concurrent Programming
2026-10-20
Halfway through the semester: why are you (still) taking this class?
Respond at: https://PollEv.com/davidbindel252 or send davidbindel252 to 22333
There are so many things that I think you should know.
And that’s why I’m bothering telling you so. …
– Dr. Seuss, Oh Say Can You Say
The best thing for being sad .. is to learn something. That is the only thing that never fails. … That is the only thing which the mind can never exhaust, never alienate, never be tortured by, never fear or distrust, and never dream of regretting.
– T.H. White, The Once and Future King
Courtesy The Marginalian.
This module: Concurrent programming with threads
and more!
| Process | Thread |
|---|---|
| Own PC and stack | Own PC and stack |
| Own heap (can share pages) | Shared heap |
| Own file descriptors | Shared file descriptors |
| Pre-emptive multitasking | Usually pre-emptive |
Consider executing on \(p\) cores vs one core:
\[\mbox{Speedup} = \frac{T_{\mbox{serial}}}{T_{\mbox{parallel}}(p)}\]
Usually don’t get perfect speedup \(p\), so
\[\mbox{Efficiency} = \frac{\mbox{Speedup}}{p}\]
NB: Can rarely get apparent efficiency > 1! Why?
How fast can things potentially go?
Amdahl’s law shows \(s\) can be a bottleneck:
\[\mbox{Speedup} = \frac{1}{s + (1-s)/p} < \frac{1}{s}\]
Strong scaling: Fixed problem size, varying \(p\)
\[\mbox{Speedup} = \frac{T_{\mbox{serial}}(n)}{T_{\mbox{parallel}}(n,p)}\]
Weak scaling: Vary size, keep work/processor constant
\[\mbox{Scaled speedup} = \frac{T_{\mbox{serial}}(n(p))}{T_{\mbox{parallel}}(n(p),p)}\]
A very different metric!
For scaled speedup, if serial work is \(s\):
\[\mbox{Scaled speedup} \leq s + (1-s)p = p - s(p-1)\]
This is Gustafson’s Law (compare Amdahl).
CS 5220: Applications of Parallel Computers
Which of these is true about parallel efficiency?
Respond at: https://PollEv.com/davidbindel252 or send davidbindel252 to 22333
thread::spawn takes FnOnce() -> T, returns JoinHandle<T>handle.join() returns thread::Result<T>main() exitErr variant on JoinHandle
Any is a trait to emulate dynamic typingSend means safe to move between threads'static lifetimeT = Result<S,E>
Ok(Err(err)) for err: Euse std::thread;
fn count_even_par(v: &[u32]) -> usize {
// Independent mappers run on parts of data
let mut handles: Vec<thread::JoinHandle<usize>> =
v.chunks(1024).map(|c| {
let c = c.to_vec(); // Can do this better!
thread::spawn(|| -> usize {
c.into_iter().filter(|x| *x % 2 == 0).count()
})}).collect();
// Reduce results at the end
handles.drain(..).map(|h| h.join().unwrap()).sum()
}use std::thread;
use rand::prelude::*;
use rand::rngs::Xoshiro256PlusPlus;
fn run_mc(ntrials: usize, p: usize) -> f64 {
let mut handles: Vec<thread::JoinHandle<f64>> =
(0..p).map(|_| {
let mut rng: Xoshiro256PlusPlus = rand::make_rng();
thread::spawn(move || {
let mut x = 0f64;
for _ in 0..ntrials {
x += run_trial(&mut rng);
}
x
})
}).collect();
let n = (p*ntrials) as f64;
handles.drain(..).map(|h| h.join().unwrap()).sum::<f64>() / n
}Do not communicate by sharing memory; instead, share memory by communicating.
– Effective Go
Putting message-passing first is not accidental!
mpscmpsc = Multiple Producer / Single Consumer(Sender, Receiver) pairuse std::sync::mpsc;
use std::thread;
fn main() {
let (tx, rx) = mpsc::channel();
let tx2 = tx.clone();
thread::spawn(move || {
tx.send(String::from("hi")).unwrap();
});
thread::spawn(move || {
tx2.send(String::from("hello")).unwrap();
});
println!("Got: {}", rx.recv().unwrap());
println!("Got: {}", rx.recv().unwrap());
}T must implement Send (most types, not all)Channels are unidirectional – communicate other way by
use std::sync::mpsc::{channel, Receiver, RecvError, Sender};
use std::thread;
struct Command(String, Sender<String>);
fn echo_client(id: usize, tx: Sender<Command>) {
let (response_tx, response_rx) = channel();
tx.send(Command(format!("Ping {id}"), response_tx)).unwrap();
println!("{id} received: {}", response_rx.recv().unwrap());
}
fn echo_server(server_rx: Receiver<Command>) -> Result<(), RecvError> {
loop {
let Command(s, tx) = server_rx.recv()?;
println!("Server received: {s}");
tx.send(s).unwrap();
}
}fn main() {
let (server_tx, server_rx) = channel();
let mut clients: Vec<thread::JoinHandle<()>> = Vec::new();
thread::spawn(move || echo_server(server_rx));
for i in 0..10 {
let tx = server_tx.clone();
clients.push(thread::spawn(move || echo_client(i, tx)));
}
for c in clients.drain(..) {
c.join().unwrap()
}
}