从零实现Rust异步运行时:Future/Waker/Reactor三部曲
一、引言
Rust 的 async/await 是零成本抽象的典范------不依赖 GC,不分配堆内存(除非你主动),却提供了类似 Go goroutine 的并发体验。但这一切的底层,是一个完整的异步运行时:Future 状态机 → Waker 唤醒机制 → Reactor 事件循环。
本文将带你从零实现一个迷你 Tokio 运行时。我们从最底层的 Future trait 开始,手写 Poll/Waker/Context 三部曲,然后实现 Re-actor(epoll 事件驱动)和 Executor(任务调度),最终让 async fn、tokio::spawn、TcpListener 等在你自己的运行时上运行。
二、Future:编译器自动展开的状态机
2.1 The Trait
Future trait 是 Rust 异步的核心------极其简洁,只有一个方法:
rust
use std::task::{Context, Poll};
use std::pin::Pin;
use std::future::Future;
// std::future::Future 的定义(简化版):
pub trait Future {
type Output;
// ★ 唯一的方法:推进Future直到完成
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll;
}
// Poll 枚举:
// Poll::Pending → "我还没准备好,稍后再调用我"
// Poll::Ready(T) → "我完成了,这是结果"
2.2 手写一个Future
rust
use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};
use std::time::{Duration, Instant};
/// 一个简单Future:在指定时间后返回
struct TimerFuture {
deadline: Instant,
}
impl TimerFuture {
fn new(duration: Duration) -> Self {
TimerFuture {
deadline: Instant::now() + duration,
}
}
}
impl Future for TimerFuture {
type Output = ();
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
if Instant::now() >= self.deadline {
// 时间到 → 完成
println!("TimerFuture: 时间到!");
Poll::Ready(())
} else {
// 时间未到 → 注册唤醒器,返回Pending
// ★ 关键: 让Waker能在未来唤醒这个Future
println!("TimerFuture: 还没到时间,注册Waker...");
cx.waker().wake_by_ref(); // 简化版:立即重新调度
// 实际实现中应该:
// 1. 将Waker存入某个等待队列
// 2. 设置定时器,到期后调用waker.wake()
Poll::Pending
}
}
}
2.3 async fn 展开为状态机
rust
// 你写的代码:
async fn fetch_and_process(url: &str) -> String {
let response = reqwest::get(url).await; // .await点1
let body = response.text().await; // .await点2
format!("Length: {}", body.len())
}
// 编译器展开后(概念代码):
enum FetchAndProcessFuture<'a> {
// 状态0: 初始状态,等待第一个Future
State0 { url: &'a str },
// 状态1: 等待 response.text()
State1 { response: Response },
// 状态2: 完成
Done,
}
impl<'a> Future for FetchAndProcessFuture<'a> {
type Output = String;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll {
loop {
match &mut *self {
FetchAndProcessFuture::State0 { url } => {
// 启动第一个Future
let fut = reqwest::get(url);
// ★ 进入状态1,保存中间结果
*self = FetchAndProcessFuture::State1 {
response: /* 需要实际poll fut */
};
// 继续循环,因为状态切换了
}
FetchAndProcessFuture::State1 { response } => {
// 等待第二个Future
let fut = response.text();
match fut.poll(cx) {
Poll::Ready(body) => {
*self = FetchAndProcessFuture::Done;
return Poll::Ready(format!("Length: {}", body.len()));
}
Poll::Pending => return Poll::Pending,
}
}
FetchAndProcessFuture::Done => {
panic!("Future already completed");
}
}
}
}
}
// ★ 每个 .await 都是一个状态转换点
// 编译器自动生成这个状态机,无需手写match
// 状态的跨点变量(url, response)被保存在枚举变体中
关键理解:
async fn返回实现了Future的匿名类型- 每个
.await点对应状态机中的一个状态 - 跨
.await的变量被保存在枚举变体中 poll()被反复调用直到返回Ready- 如果返回
Pending,Waker 会在合适时机触发下一次poll()
三、Waker与Context:唤醒机制
3.1 Waker设计原理
rust
use std::task::{Waker, RawWaker, RawWakerVTable};
use std::sync::Arc;
/// Waker的内部结构(简化)
/// Waker 实际上是一个胖指针:
/// - data: *const () --- 指向任务数据的裸指针
/// - vtable: &'static RawWakerVTable --- 四个函数指针
/// RawWakerVTable 定义:
/// clone: fn(*const ()) -> RawWaker // 克隆Waker
/// wake: fn(*const ()) // 唤醒任务
/// wake_by_ref: fn(*const ()) // 引用唤醒
/// drop: fn(*const ()) // 释放资源
pub struct MiniWaker {
// Waker 本质上是对某个可唤醒实体的引用
// 在Tokio中,这个实体是 Task(包含Future+状态)
task_ptr: *const (),
vtable: &'static RawWakerVTable,
}
// ★ 为什么要这么复杂(胖指针+vtable)?
// 因为Waker需要:
// 1. 可以被安全地跨线程发送 (Send + Sync)
// 2. 可以被克隆(多个Waker指向同一任务)
// 3. 不依赖具体类型(类型擦除)
// 4. 零成本抽象(无虚函数调用开销)
3.2 创建Waker
rust
use std::task::{RawWaker, RawWakerVTable, Waker};
// 为任务创建Waker
fn create_waker(task: Arc) -> Waker {
// ★ 关键:将Arc 转为裸指针 → 存入RawWaker.data
let raw = Arc::into_raw(task) as *const ();
// ★ vtable: 四个函数告诉Waker如何操作任务
unsafe fn clone_raw(data: *const ()) -> RawWaker {
// clone: 增加Arc引用计数
let arc = Arc::from_raw(data as *const Task);
std::mem::forget(arc.clone());
std::mem::forget(arc);
RawWaker::new(data, &VTABLE)
}
unsafe fn wake_raw(data: *const ()) {
// wake: 将任务重新加入执行队列
let arc = Arc::from_raw(data as *const Task);
arc.executor.schedule(arc.clone());
}
unsafe fn wake_by_ref_raw(data: *const ()) {
// wake_by_ref: 不消耗Arc
let arc = Arc::from_raw(data as *const Task);
let arc_clone = arc.clone();
std::mem::forget(arc);
arc_clone.executor.schedule(arc_clone);
}
unsafe fn drop_raw(data: *const ()) {
// drop: 减少引用计数
drop(Arc::from_raw(data as *const Task));
}
static VTABLE: RawWakerVTable = RawWakerVTable::new(
clone_raw, wake_raw, wake_by_ref_raw, drop_raw
);
let raw_waker = RawWaker::new(raw, &VTABLE);
unsafe { Waker::from_raw(raw_waker) }
}
3.3 Waker 的完整生命周期
1. 创建: 当Future第一次被poll时,通过Context传入Waker
future.poll(cx) → cx.waker() → 获取Waker的引用
2. 存储: Future将Waker保存起来(通常通过闭包/回调)
self.waker = Some(cx.waker().clone())
3. 等待: Future返回Poll::Pending, 执行器挂起此任务
4. 唤醒: 当事件发生时(定时器触发/IO就绪/通道有消息)
stored_waker.wake() → Waker.vtable.wake(task_ptr)
→ executor.schedule(task) → 任务重新进入就绪队列
5. 重新Poll: 执行器从就绪队列取出任务
future.poll(cx) → 这次可能返回Poll::Ready
6. 完成: Future返回Poll::Ready → 任务结束
四、Reactor:事件驱动核心
4.1 epoll 事件循环
rust
use std::collections::HashMap;
use std::io;
use std::os::unix::io::RawFd;
use std::sync::{Arc, Mutex};
/// Reactor: 基于epoll的事件循环(Linux)
/// 对应Tokio中的driver/IO driver
pub struct Reactor {
epoll_fd: RawFd, // epoll实例的文件描述符
wakers: Arc>>, // token → Waker 映射
}
impl Reactor {
pub fn new() -> io::Result {
// epoll_create1(0) 创建一个epoll实例
let epoll_fd = unsafe { libc::epoll_create1(0) };
if epoll_fd < 0 {
return Err(io::Error::last_os_error());
}
Ok(Reactor {
epoll_fd,
wakers: Arc::new(Mutex::new(HashMap::new())),
})
}
/// 注册IO事件监听 + 关联的Waker
pub fn register(
&self,
fd: RawFd, // 要监听的fd
token: u64, // 事件标识符
interests: i32, // EPOLLIN | EPOLLOUT 等
waker: Waker // ★ 关联的唤醒器
) -> io::Result<()> {
// 1. 将Waker存入HashMap(token→Waker映射)
self.wakers.lock().unwrap().insert(token, waker);
// 2. 向epoll注册fd
let mut event = libc::epoll_event {
events: interests as u32,
u64: token, // ★ token被存入epoll_event
};
let ret = unsafe {
libc::epoll_ctl(
self.epoll_fd,
libc::EPOLL_CTL_ADD,
fd,
&mut event
)
};
if ret < 0 {
return Err(io::Error::last_os_error());
}
Ok(())
}
/// 事件循环: 阻塞等待IO事件 → 唤醒对应Waker
pub fn event_loop(&self) -> io::Result<()> {
let mut events: Vec = vec![
libc::epoll_event { events: 0, u64: 0 };
1024
];
loop {
// ★ epoll_wait: 阻塞等待事件
let n_events = unsafe {
libc::epoll_wait(
self.epoll_fd,
events.as_mut_ptr(),
events.len() as i32,
-1 // -1 = 无限等待; 正数 = 超时毫秒
)
};
if n_events < 0 {
let err = io::Error::last_os_error();
if err.kind() == io::ErrorKind::Interrupted {
continue; // 被信号中断,继续等待
}
return Err(err);
}
// 处理就绪的事件
for i in 0..n_events as usize {
let event = &events[i];
let token = event.u64;
// ★ 根据token找到对应的Waker → 唤醒
if let Some(waker) = self.wakers.lock().unwrap().get(&token) {
waker.wake_by_ref();
}
}
}
}
}
/// Reactor 单例(全局共享)
/// 对应 Tokio 中的 Handle::current()
lazy_static::lazy_static! {
static ref REACTOR: Arc = Arc::new(Reactor::new().unwrap());
}
// ============ 使用示例 ============
use std::net::TcpListener;
use std::os::unix::io::AsRawFd;
async fn read_from_socket(listener: &TcpListener) -> io::Result> {
// 1. 创建SocketFuture(见下文)
let mut fut = SocketReadFuture::new(listener);
// 2. await 会调用 fut.poll()
// poll内部向Reactor注册Waker+epoll事件
// 然后返回Poll::Pending
// 3. Reactor在fd可读时调用Waker.wake()
// 执行器重新调度此任务
// 4. poll再次被调用,读取数据并返回Ready
fut.await
}
struct SocketReadFuture {
listener: TcpListener,
registered: bool,
}
impl Future for SocketReadFuture {
type Output = io::Result>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll {
// 1. 首次poll: 向Reactor注册
if !self.registered {
let fd = self.listener.as_raw_fd();
let waker = cx.waker().clone();
// ★ 注册: 告诉Reactor"当这个fd可读时,用这个Waker唤醒我"
REACTOR.register(
fd,
fd as u64, // token = fd
libc::EPOLLIN, // 关注"可读"事件
waker // ★ Waker关联
).unwrap();
self.registered = true;
}
// 2. 尝试读取
let mut buf = vec![0u8; 1024];
match self.listener.accept() {
Ok((mut stream, _)) => {
match stream.read(&mut buf) {
Ok(n) => {
buf.truncate(n);
Poll::Ready(Ok(buf))
}
Err(e) if e.kind() == io::ErrorKind::WouldBlock => {
// 数据还没准备好 → Pending
Poll::Pending
}
Err(e) => Poll::Ready(Err(e)),
}
}
Err(e) if e.kind() == io::ErrorKind::WouldBlock => {
Poll::Pending
}
Err(e) => Poll::Ready(Err(e)),
}
}
}
4.2 epoll 水平触发 vs 边缘触发
rust
// EPOLL Level Triggered (LT, 默认):
// - 只要fd可读,每次都通知
// - 即使上次没读完数据,下次epoll_wait还会通知
// - 编程简单,不容易丢事件
let events = libc::EPOLLIN; // 默认LT
// EPOLL Edge Triggered (ET, 性能更好):
// - 只在fd状态变化时通知一次
// - 必须读完所有数据,否则后续不再通知
// - 需要非阻塞IO + 循环读取直到EAGAIN
let events = libc::EPOLLIN | libc::EPOLLET;
// ET模式正确读法:
loop {
let n = stream.read(&mut buf);
match n {
Ok(0) => break, // EOF
Ok(_) => continue, // 继续读
Err(e) if e.kind() == WouldBlock => break, // 全部读完
Err(e) => return Err(e),
}
}
五、Executor:任务调度器
5.1 简化版执行器
rust
use std::cell::RefCell;
use std::collections::VecDeque;
use std::future::Future;
use std::pin::Pin;
use std::rc::Rc;
use std::task::{Context, Poll};
/// 迷你Tokio执行器
pub struct MiniTokio {
/// 就绪任务队列
ready_queue: Rc>>,
}
/// 任务:包含一个Future + 执行器引用
struct Task {
future: Pin>>,
}
impl MiniTokio {
pub fn new() -> Self {
MiniTokio {
ready_queue: Rc::new(RefCell::new(VecDeque::new())),
}
}
/// ★ spawn: 类似 tokio::spawn
pub fn spawn(&self, future: F)
where
F: Future + 'static,
{
let task = Task {
future: Box::pin(future),
};
self.ready_queue.borrow_mut().push_back(task);
}
/// ★ block_on: 阻塞运行事件循环
pub fn block_on(&self, future: F) -> F::Output
where
F: Future,
{
// 创建"立即返回"的Waker(简化版)
// 实际Tokio中,Waker将任务放回就绪队列
let waker = create_dummy_waker();
let mut cx = Context::from_waker(&waker);
// Pin包装主Future
let mut main_future = Box::pin(future);
loop {
// 1. 先poll主Future
match main_future.as_mut().poll(&mut cx) {
Poll::Ready(result) => return result,
Poll::Pending => {}
}
// 2. 处理就绪任务队列
let mut queue = self.ready_queue.borrow_mut();
while let Some(mut task) = queue.pop_front() {
match task.future.as_mut().poll(&mut cx) {
Poll::Ready(()) => {
// 任务完成,丢弃
println!("Task completed!");
}
Poll::Pending => {
// 任务还没完成,放回队列尾部
queue.push_back(task);
}
}
}
// 3. 如果队列空了,可以让出CPU或等待IO事件
if queue.is_empty() {
// 实际实现中: 调用Reactor.event_loop()阻塞等待
std::thread::sleep(std::time::Duration::from_millis(1));
}
}
}
}
// ============ 完整使用示例 ============
fn main() {
let rt = MiniTokio::new();
rt.block_on(async {
println!("Hello from async!");
// spawn 并发任务
rt.spawn(async {
for i in 0..5 {
println!("Task 1: {}", i);
// 模拟IO等待
TimerFuture::new(Duration::from_millis(100)).await;
}
});
rt.spawn(async {
for i in 0..5 {
println!("Task 2: {}", i);
TimerFuture::new(Duration::from_millis(200)).await;
}
});
println!("Main task done!");
});
}
六、io_uring:下一代异步IO
rust
// io_uring 是 Linux 5.1+ 的新异步IO接口
// 相比 epoll:
// - epoll: 用户态→内核态→用户态 3次上下文切换
// - io_uring: 共享环形缓冲区,零系统调用(理论上)
// 核心概念:
// SQ (Submission Queue): 用户态写入IO请求
// CQ (Completion Queue): 内核态写入完成事件
use io_uring::{IoUring, opcode, types};
fn io_uring_example() -> io::Result<()> {
// 创建io_uring实例(队列深度256)
let mut ring = IoUring::new(256)?;
// 打开文件
let file = std::fs::File::open("test.txt")?;
let fd = file.as_raw_fd();
// 分配缓冲区
let mut buf = vec![0u8; 4096];
// ★ 向SQ提交读请求
let read_op = opcode::Read::new(
types::Fd(fd),
buf.as_mut_ptr(),
buf.len() as u32
);
unsafe {
ring.submission()
.push(&read_op.build().user_data(0x42))
.expect("SQ full");
}
// ★ 提交到内核(单次系统调用提交多个请求)
ring.submit()?;
// ★ 等待完成
let cqe = ring.completion().next().expect("No completion");
assert_eq!(cqe.user_data(), 0x42);
let bytes_read = cqe.result() as usize;
println!("Read {} bytes: {:?}", bytes_read, &buf[..bytes_read]);
Ok(())
}
// Tokio 正在用 io_uring 重写 IO driver(tokio-uring)
// 预期在高并发IO场景下,延迟降低30-50%
七、总结
从零实现了Rust异步运行时的完整三部曲:
1. Future状态机:
async fn→ 编译器展开为枚举状态机- 每个
.await是一个状态转换点 - 跨await变量保存在枚举变体中
2. Waker唤醒机制:
RawWaker胖指针 (data + vtable)wake()→ 任务重新加入就绪队列wake_by_ref()→ 不消耗Waker
3. Reactor+Executor:
- Reactor:
epoll_wait阻塞等待 → 根据token查找Waker →wake() - Executor: 就绪队列 →
poll()→Pending放回队列 /Ready丢弃
理解这三者,你就理解了 Tokio、async-std、smol 等所有 Rust 异步运行时的内核。它们只是在调度策略、IO驱动、工作窃取等方面做了更多优化,但核心骨架完全一致。