
大家好,上期我们讲了为什么 Rust 需要 Tokio 这个异步运行时:async/await 只是语法糖,真正让它跑起来的,是运行时(Runtime)。今天我们就深入拆解 Tokio 的三大核心概念——Future、Task 与 Runtime。弄懂它们,你就真正理解了 Tokio 为什么能用少量线程驱动海量并发。

所有示例已在 Tokio 1.50(2026 年最新稳定版)上验证通过。
Rust 的异步模型与 Go 或 Node.js 有本质区别:Rust 的异步是 Pull(拉)模型。Future 被创建后如果不去 poll(拨动)它,它什么都不会做。
Future 是 Rust 异步编程的核心 trait,定义在 std::future::Future:
pub trait Future {
type Output;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output>;
}Poll 只有两种状态:Ready(T)(已完成,返回结果)或 Pending(未就绪,需要等待)。poll 方法:尝试推进 Future 的执行。如果还没准备好,就返回 Pending 并注册唤醒器(Waker)。async fn 在编译时会被自动转换成一个状态机(enum),每个 .await 点就是一个状态。
举个例子:
async fn fetch_data() -> String {
let resp = reqwest::get("https://example.com").await.unwrap(); // 第一次 .await
let text = resp.text().await.unwrap(); // 第二次 .await
text
}编译后大致等价于一个 enum:
enum FetchData {
Init, // 初始状态
WaitingForResponse(...), // 第一个 .await 后的状态
WaitingForText(...), // 第二个 .await 后的状态
Done(String), // 完成
}poll,状态机就从当前状态往前走一步,直到返回 Ready。关键点:Future 本身不会自动执行,必须有人反复调用 poll 才能前进。这就是 Runtime 的工作。

单个 Future 很脆弱,Tokio 把 Future 包装成 Task 后,它才拥有了生命。
Task = Future + 调度上下文(Waker + 其他元数据)
#[tokio::main]
async fn main() {
let handle = tokio::spawn(async {
// 这个匿名 Future 被包装成 Task
println!("我在 Task 里运行!");
"done"
});
let result = handle.await.unwrap(); // 等待 Task 完成
println!("Task 返回: {}", result);
}JoinHandle 是 Task 的句柄,可用于 .await 或 abort。spawn 出来的 Task 可以并发执行,即使只用几个 OS 线程。Waker 机制(最优雅的部分):
当 Future 返回 Pending 时,它必须将 Context 中的 Waker 注册到某个地方(比如 Reactor 或定时器)。
关键点:如果返回了 Pending 却没有注册唤醒逻辑,这个 Task 就会变成“僵尸”,永远不会被再次 poll
这就实现了零成本的暂停与恢复。
Runtime 是 Tokio 的大脑,包含两大子系统:
poll 哪个 Task。poll),Tokio 内部有 Budget 机制。一旦操作次数超限,即使没遇到 .await 也会强制 yield,确保公平性。Runtime 还内置:
spawn_blocking)一句话总结:Executor 负责把任务压榨干,Reactor 负责在任务等待时盯着门口。
tokio::spawn → 创建 Task(包裹 Future)Future::pollPending → 注册 Waker 到 Reactorwake()整个过程无线程阻塞、无上下文切换,并发能力爆炸。
我们可以手写一个 Future 来观察这个过程。注意:为了教学,我们手动模拟了唤醒。
use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};
use std::time::{Duration, Instant};
struct MyLazyFuture {
start_time: Instant,
}
impl Future for MyLazyFuture {
type Output = &'static str;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
if self.start_time.elapsed() >= Duration::from_secs(1) {
println!("I/O 就绪,返回 Ready");
Poll::Ready("Done!")
} else {
println!("尚未就绪,手动注册唤醒...");
// 真实场景下由 Reactor 自动完成,这里我们模拟 100ms 后唤醒自己
let waker = cx.waker().clone();
tokio::spawn(async move {
tokio::time::sleep(Duration::from_millis(100)).await;
waker.wake();
});
Poll::Pending
}
}
}
#[tokio::main]
async fn main() {
let future = MyLazyFuture { start_time: Instant::now() };
let result = tokio::spawn(future).await.unwrap();
println!("最终结果: {}", result);
}
这就是状态机在 Runtime 驱动下的真实执行过程。
tokio::task::spawn_blocking。tokio::sync::Mutex。总结
三者协作,才让 Rust 异步编程既安全又高效。
掌握了这三个概念,你已经站在了 Tokio 的门槛上。后面我们会用它们去写 TCP 服务器、Channel 通信、高性能 Web 服务。
我们下期见!
(完)