2024-05-16 02:00:48 +08:00
|
|
|
# Runtime architecture
|
|
|
|
|
|
|
|
So far we've been talking about async runtimes as an abstract concept.
|
|
|
|
Let's dig a bit deeper into the way they are implemented—as you'll see soon enough,
|
|
|
|
it has an impact on our code.
|
|
|
|
|
|
|
|
## Flavors
|
|
|
|
|
2024-05-24 23:00:03 +08:00
|
|
|
`tokio` ships two different runtime _flavors_.
|
2024-05-16 02:00:48 +08:00
|
|
|
|
2024-05-24 23:00:03 +08:00
|
|
|
You can configure your runtime via `tokio::runtime::Builder`:
|
2024-05-16 02:00:48 +08:00
|
|
|
|
|
|
|
- `Builder::new_multi_thread` gives you a **multithreaded `tokio` runtime**
|
|
|
|
- `Builder::new_current_thread` will instead rely on the **current thread** for execution.
|
|
|
|
|
|
|
|
`#[tokio::main]` returns a multithreaded runtime by default, while
|
|
|
|
`#[tokio::test]` uses a current thread runtime out of the box.
|
|
|
|
|
|
|
|
### Current thread runtime
|
|
|
|
|
|
|
|
The current-thread runtime, as the name implies, relies exclusively on the OS thread
|
2024-05-24 23:00:03 +08:00
|
|
|
it was launched on to schedule and execute tasks.\
|
2024-05-16 02:00:48 +08:00
|
|
|
When using the current-thread runtime, you have **concurrency** but no **parallelism**:
|
|
|
|
asynchronous tasks will be interleaved, but there will always be at most one task running
|
|
|
|
at any given time.
|
|
|
|
|
|
|
|
### Multithreaded runtime
|
|
|
|
|
2024-05-24 23:00:03 +08:00
|
|
|
When using the multithreaded runtime, instead, there can up to `N` tasks running
|
|
|
|
_in parallel_ at any given time, where `N` is the number of threads used by the
|
|
|
|
runtime. By default, `N` matches the number of available CPU cores.
|
2024-05-16 02:00:48 +08:00
|
|
|
|
2024-05-24 23:00:03 +08:00
|
|
|
There's more: `tokio` performs **work-stealing**.\
|
2024-05-16 02:00:48 +08:00
|
|
|
If a thread is idle, it won't wait around: it'll try to find a new task that's ready for
|
|
|
|
execution, either from a global queue or by stealing it from the local queue of another
|
2024-05-24 23:00:03 +08:00
|
|
|
thread.\
|
|
|
|
Work-stealing can have significant performance benefits, especially on tail latencies,
|
2024-05-16 02:00:48 +08:00
|
|
|
whenever your application is dealing with workloads that are not perfectly balanced
|
|
|
|
across threads.
|
|
|
|
|
|
|
|
## Implications
|
|
|
|
|
2024-05-24 23:00:03 +08:00
|
|
|
`tokio::spawn` is flavor-agnostic: it'll work no matter if you're running on the multithreaded
|
|
|
|
or current-thread runtime. The downside is that the signature assume the worst case
|
2024-05-16 02:00:48 +08:00
|
|
|
(i.e. multithreaded) and is constrained accordingly:
|
|
|
|
|
|
|
|
```rust
|
|
|
|
pub fn spawn<F>(future: F) -> JoinHandle<F::Output>
|
|
|
|
where
|
|
|
|
F: Future + Send + 'static,
|
|
|
|
F::Output: Send + 'static,
|
|
|
|
{ /* */ }
|
|
|
|
```
|
|
|
|
|
2024-05-24 23:00:03 +08:00
|
|
|
Let's ignore the `Future` trait for now to focus on the rest.\
|
2024-05-16 02:00:48 +08:00
|
|
|
`spawn` is asking all its inputs to be `Send` and have a `'static` lifetime.
|
|
|
|
|
|
|
|
The `'static` constraint follows the same rationale of the `'static` constraint
|
|
|
|
on `std::thread::spawn`: the spawned task may outlive the context it was spawned
|
|
|
|
from, therefore it shouldn't depend on any local data that may be de-allocated
|
|
|
|
after the spawning context is destroyed.
|
|
|
|
|
|
|
|
```rust
|
|
|
|
fn spawner() {
|
|
|
|
let v = vec![1, 2, 3];
|
|
|
|
// This won't work, since `&v` doesn't
|
|
|
|
// live long enough.
|
|
|
|
tokio::spawn(async {
|
|
|
|
for x in &v {
|
|
|
|
println!("{x}")
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
`Send`, on the other hand, is a direct consequence of `tokio`'s work-stealing strategy:
|
|
|
|
a task that was spawned on thread `A` may end up being moved to thread `B` if that's idle,
|
|
|
|
thus requiring a `Send` bound since we're crossing thread boundaries.
|
|
|
|
|
|
|
|
```rust
|
|
|
|
fn spawner(input: Rc<u64>) {
|
|
|
|
// This won't work either, because
|
|
|
|
// `Rc` isn't `Send`.
|
|
|
|
tokio::spawn(async move {
|
|
|
|
println!("{}", input);
|
|
|
|
})
|
|
|
|
}
|
2024-05-24 23:00:03 +08:00
|
|
|
```
|