从零实现Rust异步运行时:Future/Waker/Reactor三部曲

从零实现Rust异步运行时:Future/Waker/Reactor三部曲

一、引言

Rust 的 async/await 是零成本抽象的典范------不依赖 GC,不分配堆内存(除非你主动),却提供了类似 Go goroutine 的并发体验。但这一切的底层,是一个完整的异步运行时:Future 状态机 → Waker 唤醒机制 → Reactor 事件循环

本文将带你从零实现一个迷你 Tokio 运行时。我们从最底层的 Future trait 开始,手写 Poll/Waker/Context 三部曲,然后实现 Re-actor(epoll 事件驱动)和 Executor(任务调度),最终让 async fntokio::spawnTcpListener 等在你自己的运行时上运行。

二、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)被保存在枚举变体中

关键理解:

  1. async fn 返回实现了 Future 的匿名类型
  2. 每个 .await 点对应状态机中的一个状态
  3. .await 的变量被保存在枚举变体中
  4. poll() 被反复调用直到返回 Ready
  5. 如果返回 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驱动、工作窃取等方面做了更多优化,但核心骨架完全一致

相关推荐
上海云盾-小余2 小时前
混合网络攻击深度解析:WAF 流量清洗落地完整方案
网络
鹿角片ljp2 小时前
Java框架篇:Spring + SpringMVC + SpringBoot + MyBatis深度复习
java·开发语言
xiaoxiangsiyan2 小时前
LNMP + Redis Sentinel 高可用架构部署手册(续)
运维·网络·数据库·redis·缓存·架构·sentinel
xlxxy_3 小时前
外部系统调用SAP接口遇到的一些报错
开发语言·数据库·sap·abap
八角.。3 小时前
方法参数与Debug按键
java·开发语言·jvm
郝亚军3 小时前
FE1.1S-BQFN24BT可以指定IP端口吗?
开发语言·php
RD_daoyi3 小时前
Google核心算法不再通知!全年持续滚动更新
大数据·服务器·前端·网络·搜索引擎·.net
阿里嘎多学长3 小时前
2026-08-04 GitHub 热点项目精选
开发语言·程序员·github·代码托管
晴天163 小时前
Rust 快速入门 (从 JavaScript 开发者视角)-Day09
开发语言·javascript·rust
梦醒沉醉4 小时前
13、Rust程序设计语言——认识Cargo和Crates.io
rust