Theme
Q072. How does the Tokio runtime schedule tasks?
Short Answer
Tokio stores spawned futures as tasks and polls ready tasks on executor workers. A task that returns Pending is not polled again until a waker marks it ready. The multi-thread runtime uses worker-local queues, a shared queue, and work stealing to distribute runnable tasks; the current-thread runtime polls all tasks on one thread. Scheduling is cooperative, so tasks must yield through pending awaits or explicit yielding.
Deep Dive
Tokio combines task scheduling with a timer and I/O driver. Readiness events wake tasks, which are queued for polling. Worker-local queues improve cache locality, while stealing helps balance uneven workloads.
Tokio also uses cooperative budgeting to stop one task from consuming unlimited immediately-ready operations without giving the scheduler a chance to run peers. This improves fairness but does not preempt arbitrary synchronous loops; such loops must yield or move off the executor.
Scheduling order is not a stable application contract. Correct code must not depend on a particular worker, task order, or immediate execution after spawn. Shared state still requires synchronization, and task-local data should be used when thread-local assumptions are invalid.
Internal Model
A task contains its pinned future, readiness state, scheduling links, and completion data. A waker atomically transitions or marks readiness and enqueues the task when necessary.
The runtime builder chooses current-thread or multi-thread scheduling and configures worker counts, blocking limits, drivers, and other runtime behavior.
Example
rust
#[tokio::main(flavor = "multi_thread", worker_threads = 2)]
async fn main() {
let first = tokio::spawn(async {
tokio::task::yield_now().await;
20_u64
});
let second = tokio::spawn(async { 22_u64 });
assert_eq!(first.await.unwrap() + second.await.unwrap(), 42);
}Common Mistakes
- Assuming Tokio preempts long synchronous task code.
- Depending on a spawned task running immediately or on one specific worker.
- Treating work stealing as a guarantee of equal per-task latency.
Follow-ups
- What problem does cooperative budgeting solve?
- How does the current-thread runtime differ operationally?
- What happens between an I/O readiness event and the next task poll?
Related
- Q044. How does an OS thread differ from an async task?
- Q060. What is a
Waker, and why does a future need one? - Q071. Why is blocking code dangerous inside async tasks?
References
- Tokio Documentation
- Async Book