Rust Concurrency Cheat Sheet
Safety
Rust ensures data race safety through the type system (Send
and Sync
marker traits) as well as the ownership and borrowing rules: it is not allowed to alias a mutable reference, so it is not possible to perform a data race.
Overview
Problem | |
---|---|
Parallelism | Multi-core utilization |
Concurrency | Single-core idleness |
Solution | Primitive | Type | Description | Examples | |
---|---|---|---|---|---|
Parallelism | Multithreading | Thread | T: Send |
Do work simultaneously on different threads | std::thread::spawn |
Concurrency | Single-threaded concurrency | Future | Future |
Futures run concurrently on the same thread | futures::future::join , futures::join |
Concurrency +Parallelism |
Multithreaded concurrency | Task | T: Future + Send |
Tasks run concurrently to other tasks; the task may run on the current thread, or it may be sent to a different thread | async_std::task::spawn , tokio::spawn |
Futures
pub trait Future {
type Output;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output>;
}
pub enum Poll {
Ready(T),
Pending,
}
Future
has to bepoll
ed (by the executor) to resume where it last yielded and make progress (async is lazy)&mut Self
contains state (state machine)Pin
the memory location because the future contains self-referential dataContext
contains theWaker
to notify the executor that progress can be made- async/await on futures is implemented by generators
async fn
andasync
blocks returnimpl Future
- calling
.await
attempts to resolve theFuture
: if theFuture
is blocked, it yields control; if progress can be made, theFuture
resumes
Futures form a tree of futures. The leaf futures commmunicate with the executor. The root future of a tree is called a task.
Share state
Threads | Tasks | |
---|---|---|
channel | std::sync::mpsc (Send ), crossbeam::channel (Send , Sync ) |
tokio::sync::mpsc , tokio::sync::oneshot , tokio::sync::broadcast , tokio::sync::watch , async_channel::unbounded , async_channel::bounded |
mutex | std::sync::Mutex |
tokio::sync::Mutex |
Marker traits
Send
: safe to send it to another threadSync
: safe to share between threads
Type | Send |
Sync |
---|---|---|
Rc |
No | No |
Arc |
Yes (if T is Send ) |
Yes (if T is Sync ) |
Mutex |
Yes (if T is Send ) |
Yes (if T is Send ) |
RwLock |
Yes (if T is Send ) |
Yes (if T is Send and Sync ) |
Concurrency models
Model | Description |
---|---|
shared memory | threads operate on regions of shared memory |
worker pools | many identical threads receive jobs from a shared job queue |
actors | many different job queues, one for each actor; actors communicate exclusively by exchanging messages |
Runtime | Description |
---|---|
tokio (multithreaded) | thread pool with work-stealing scheduler: each processor maintains its own run queue; idle processor checks sibling processor run queues, and attempts to steal tasks from them |
actix_rt | single-threaded async runtime; futures are !Send |
actix | actor framework |
actix-web | constructs an application instance for each thread; application data must be constructed multiple times or shared between threads |
Terminology
Data race: Two or more threads concurrently accessing a location of memory; one or more of them is a write; one or more of them is unsynchronized.
Race condition: The condition of a software system where the system's substantive behavior is dependent on the sequence or timing of other uncontrollable events.
Deadlock: Any situation in which no member of some group of entities can proceed because each waits for another member, including itself, to take action.
Heisenbug: A heisenbug is a software bug that seems to disappear or alter its behavior when one attempts to study it. For example, time-sensitive bugs such as race conditions may not occur when the program is slowed down by single-stepping source lines in the debugger.
Marker trait: Used to give the compiler certain guarantees (see std::marker
).
Thread: A native OS thread.
Green threads (or virtual threads): Threads that are scheduled by a runtime library or virtual machine (VM) instead of natively by the underlying operating system (OS).
Context switch: The process of storing the state of a process or thread, so that it can be restored and resume execution at a later point.
Synchronous I/O: blocking I/O.
Asynchronous I/O: non-blocking I/O.
Future (cf. promise): A single value produced asynchronously.
Stream: A series of values produced asynchronously.
Sink: Write data asynchronously.
Task: An asynchronous green thread.
Channel: Enables communication between threads or tasks.
Mutex (mutual exclusion): Shares data between threads or tasks.
Executor: Runs asynchronous tasks.
Generator: Used internally by the compiler. Can stop (or yield) its execution and resume (poll
) afterwards from its last yield point by inspecting the previously stored state in self
.
Reactor: Leaf futures register event sources with the reactor.
Runtime: Bundles a reactor and an executor.
poll
ing: Attempts to resolve the future into a final value.
io_uring: A Linux kernel system call interface for storage device asynchronous I/O operations.
References
- Steve Klabnik and Carol Nichols, The Rust Programming Language
- Jon Gjengset, Rust for Rustaceans
- The Rustonomicon
- Asynchronous Programming in Rust
- Tokio tutorial
- Tokio's work-stealing scheduler
- Actix user guide