异步编程的底层机制

原理

Rust 异步运行时基于协作式调度(cooperative scheduling)。async fn 在编译时被转换为实现了 Future trait 的状态机——每个 .await 点对应状态机的一个状态。

Future trait 的 poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> 方法被 executor 调用。Future 返回 Poll::Pending 表示未就绪,并通过 cx.waker().wake() 注册回调;返回 Poll::Ready(v) 表示完成。

Pin 保证 Future 在内存中不移动(自引用结构体的安全要求)。编译器生成的匿名类型包含所有跨 .await 的局部变量。

Executor(如 tokio)内部维护就绪队列和 IO 事件循环(epoll/io_uring)。tokio::spawn 将 Future 提交到线程池的任务队列中。

与 OS 线程对比:异步 Task 切换无内核参与(用户态切换),单个线程可管理数万个并发连接(C10K/C100K 问题解决)。


语法

// async fn — 编译器自动生成 Future 状态机
async fn fetch_url(url: &str) -> Result<String, Error> {
    let resp = reqwest::get(url).await?;
    resp.text().await
}
 
// tokio 运行时
#[tokio::main]
async fn main() {
    let result = fetch_url("https://example.com").await.unwrap();
    println!("{}", result);
}
 
// 并发等待
let (a, b) = tokio::join!(task1(), task2());
 
// select — 竞态
tokio::select! {
    result = task_a() => { println!("a: {:?}", result); }
    result = task_b() => { println!("b: {:?}", result); }
    _ = tokio::signal::ctrl_c() => { println!("interrupted"); }
}

Future 状态机展开(概念)

// async fn example(x: i32) -> i32 {
//     let a = do_a(x).await;
//     let b = do_b(a).await;
//     b + 1
// }
// 展开为枚举:
// enum ExampleFuture { State0 { x: i32 }, State1 { a_future: ... }, ... }
// impl Future for ExampleFuture {
//     fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<i32> { ... }
// }
异步模型线程Future
调度OS 抢占用户态协作
上下文切换~1μs (内核)~20ns (函数调用)
栈空间2MB+编译期确定(小)
并发数数千百万级
开销极低

实践

AI 自检

  1. Pin<&mut Self> 为何是 Future trait 的必要约束?自引用问题如何产生?
  2. epoll 和 io_uring 的区别?io_uring 如何进一步减少系统调用开销?