Concurrency vs. Parallelism
Concurrency is a program-structuring property where multiple independent tasks have overlapping lifetimes—making progress by interleaving execution steps or running simultaneously. Parallelism is a hardware-execution property where two or more tasks execute instructions at the exact same physical instant on separate CPU cores or SIMD execution units.
Visualizing Interleaved Concurrency vs. True Hardware Parallelism
1. Concurrency on a Single CPU Core (Time-Sliced / Cooperative Interleaving):
Core 0: [ Task A ] -> [ Task B ] -> [ Task A ] -> [ Task B ] -> [ Task A ]
Time ------------------------------------------------------------------->
(Two tasks are in progress over the same time window, but only 1 runs at any instant)
2. Parallelism on Multiple CPU Cores (Simultaneous Physical Execution):
Core 0: [ Task A -------------------------------------------- ]
Core 1: [ Task B -------------------------------------------- ]
Time ------------------------------------------------------------------->
(Both tasks execute machine instructions simultaneously on distinct cores)
Comparing Concurrency and Parallelism
| Dimension | Concurrency | Parallelism |
|---|---|---|
| Core concern | Composition and coordination of independent activities (I/O, timers, UI events, request handling) | Throughput and speedup of CPU-bound computation across multiple cores |
| Can it happen on a 1-core CPU? | Yes (via OS preemptive time-slicing or async/.await cooperative multitasking) |
No (requires $\ge 2$ physical cores, hardware threads, or vector lanes) |
| Typical workload | I/O-bound: 10,000 open network sockets waiting on packets | CPU-bound: matrix multiplication, image encoding, parallel sorting |
| Primary Rust abstractions | std::thread, async/.await futures, mpsc channels |
Scoped OS threads (std::thread::scope), work-stealing thread pools (rayon) |
Querying Available Hardware Parallelism in Rust
The standard library function std::thread::available_parallelism() queries the operating system (accounting for CPU affinity masks and container cgroup limits) to return the number of hardware threads (NonZeroUsize) that can execute in parallel on the current machine:
use std::thread;
fn parallel_chunk_sum(data: &[u64]) -> u64 {
let cores = thread::available_parallelism()
.map(|n| n.get())
.unwrap_or(1);
let chunk_size = data.len().div_ceil(cores).max(1);
thread::scope(|s| {
let mut handles = Vec::new();
for chunk in data.chunks(chunk_size) {
handles.push(s.spawn(move || chunk.iter().copied().sum::<u64>()));
}
handles
.into_iter()
.map(|h| h.join().unwrap())
.sum()
})
}
fn main() {
let numbers: Vec<u64> = (1..=1000).collect();
let total = parallel_chunk_sum(&numbers);
println!(
"available parallelism: {} hardware threads",
thread::available_parallelism().unwrap()
);
println!("parallel sum of 1..=1000: {}", total);
}
available parallelism: 10 hardware threads
parallel sum of 1..=1000: 500500
Concurrency + Parallelism Combined
Modern network servers combine both models: an multi-threaded async executor runs a fixed pool of worker threads equal to available_parallelism() (parallelism across CPU cores), while each worker thread multiplexes thousands of concurrent I/O tasks via non-blocking async/.await state machines (concurrency within each core).