跳到主要内容

async/await 基础

问题

Rust 的 async/await 是如何工作的?与其他语言的异步模型有什么区别?

答案

基础语法

// async fn 返回一个 Future
async fn fetch_data(url: &str) -> String {
// .await 暂停当前任务,让出线程
let response = reqwest::get(url).await.unwrap();
response.text().await.unwrap()
}

// 等价于返回 impl Future
fn fetch_data(url: &str) -> impl Future<Output = String> + '_ {
async move {
let response = reqwest::get(url).await.unwrap();
response.text().await.unwrap()
}
}

核心概念:Future 是惰性的

与 JavaScript 的 Promise 不同,Rust 的 Future 不会在创建时立即执行

async fn hello() {
println!("Hello!");
}

fn main() {
let future = hello(); // 什么都没发生!
// 必须交给执行器(runtime)驱动
tokio::runtime::Runtime::new().unwrap().block_on(future);
// 现在才打印 "Hello!"
}

async 块

async fn process() {
// async 块
let result = async {
let a = fetch_a().await;
let b = fetch_b().await;
a + b
}.await;

// 同时启动多个任务
let (a, b) = tokio::join!(fetch_a(), fetch_b());

// 等待第一个完成
tokio::select! {
val = fetch_a() => println!("A 先完成: {}", val),
val = fetch_b() => println!("B 先完成: {}", val),
}
}

async 与线程的对比

特性OS 线程async 任务
内存占用~8MB 栈~几百字节
上下文切换系统调用用户态
创建开销
适用并发数数千数十万
CPU 密集型❌(会阻塞运行时)

async 的编译产物

编译器将 async fn 转换为状态机

async fn example() {
let a = step_1().await; // 状态 0 → 状态 1
let b = step_2(a).await; // 状态 1 → 状态 2
println!("{}", b);
}

// 编译器生成(伪代码):
enum ExampleFuture {
State0 { /* step_1 的 future */ },
State1 { a: i32, /* step_2 的 future */ },
State2, // 完成
}

常见面试问题

Q1: Rust 的 async 和 JavaScript 的 async 有什么区别?

答案

特性RustJavaScript
Future/Promise惰性(需要 poll)立即执行
运行时不内置,需选择(Tokio)内置事件循环
底层实现编译为状态机Promise 链
多线程支持多线程运行时单线程
性能零成本抽象有分配开销

Q2: 为什么不能在 async 中使用标准库的 Mutex?

答案

标准库 Mutex::lock()阻塞当前线程。在 async 运行时中,一个线程可能跑多个任务。如果一个任务持有锁并 .await(让出线程),其他任务想获取同一个锁就会死锁。

解决方案:

  • 锁持有时间短且不跨 .await → 标准库 Mutex 没问题
  • 锁持有跨 .await → 使用 tokio::sync::Mutex

Q3: tokio::join!tokio::spawn 有什么区别?

答案

  • join!:在当前任务内并发运行多个 Future,不创建新任务
  • spawn:创建独立任务,由运行时调度到任何线程
// join!:同一任务内并发(结构化并发)
let (a, b) = tokio::join!(future_a, future_b);

// spawn:独立任务(非结构化并发)
let handle = tokio::spawn(async { /* ... */ });
let result = handle.await?;

Q4: async fn 返回的 Future 是 Send 的吗?

答案

取决于 .await 点跨越的数据是否都是 Send。如果跨 .await 持有的变量包含 RcMutexGuard 等非 Send 类型,Future 就不是 Send,无法在多线程运行时上运行。

相关链接