Rust 的 async/await 语法糖背后是怎么工作的?本文梳理一下 Tokio 运行时的核心设计。

1. Future Trait

Rust 异步的基础是 Future:

pub trait Future {
    type Output;
    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output>;
}

关键点:Future 是惰性的,不调用 poll 就不会执行。这点和 JS 的 Promise 不同。

2. Poll 的两种返回

pub enum Poll<T> {
    Ready(T),
    Pending,
}
  • Ready(value):完成,返回值
  • Pending:还没好,等会儿再来 poll

3. Waker

当 Future 返回 Pending 时,它会注册一个 Waker。资源就绪时调用 waker.wake(),调度器把这个 Future 重新放回队列等待 poll。

let waker = cx.waker().clone();
// 资源就绪时
waker.wake();

4. Tokio 的架构

Tokio 由三部分组成:

Reactor

负责 I/O 事件通知。基于 epoll(Linux)/kqueue(Mac)/IOCP(Windows)。当 socket 可读可写时,reactor 通知对应的 waker。

Executor

执行 task。Tokio 有两种 executor:

  • current_thread:单线程,task 都在当前线程跑
  • multi_thread:多线程 work-stealing,类似 Go 的 GMP

Task

async fn 包一层就成了 task:

tokio::spawn(async {
    // ...
});

5. 异步 I/O 示例

use tokio::net::TcpListener;

#[tokio::main]
async fn main() {
    let listener = TcpListener::bind("127.0.0.1:8080").await.unwrap();
    loop {
        let (mut socket, _) = listener.accept().await.unwrap();
        tokio::spawn(async move {
            let mut buf = [0; 1024];
            loop {
                match socket.read(&mut buf).await {
                    Ok(0) => return,
                    Ok(n) => {
                        if socket.write_all(&buf[..n]).await.is_err() {
                            return;
                        }
                    }
                    Err(_) => return,
                }
            }
        });
    }
}

6. 阻塞操作的陷阱

异步代码里不要直接调用阻塞操作(std:🧵:sleep、同步 I/O 等),会卡住整个 worker 线程。要用异步版本:

// 错误:阻塞
std:🧵:sleep(Duration::from_secs(1));

// 正确:异步
tokio::time::sleep(Duration::from_secs(1)).await;

实在要跑阻塞代码,用 spawn_blocking 把它丢到专门的线程池:

let result = tokio::task::spawn_blocking(|| {
    // CPU 密集或阻塞操作
    heavy_computation()
}).await.unwrap();

7. Select 多路复用

tokio::select! {
    val = receiver.recv() => {
        println!("received: {:?}", val);
    }
    _ = tokio::time::sleep(Duration::from_secs(5)) => {
        println!("timeout");
    }
}

8. Channel

Tokio 提供几种 channel:

  • mpsc:多生产者单消费者
  • oneshot:一发一收
  • broadcast:广播
  • watch:只关心最新值
let (tx, mut rx) = tokio::sync::mpsc::channel(100);
tokio::spawn(async move {
    tx.send("hello").await.unwrap();
});
while let Some(msg) = rx.recv().await {
    println!("{}", msg);
}

小结

Rust 异步的精髓在于零成本抽象:Future 是状态机,编译器把 async/await 转成 enum + poll。Tokio 在这之上提供了完整的运行时。理解 poll、waker、reactor、executor 这几个概念,写异步代码就不会懵了。