Tokio 运行时
问题
Tokio 是什么?为什么 Rust 需要外部异步运行时?
答案
为什么需要运行时
Rust 标准库只提供 Future trait 和 async/await 语法,不包含异步运行时。运行时负责:
- 驱动 Future 执行(polling)
- 提供异步 IO(网络、文件)
- 任务调度和线程管理
- 定时器、同步原语等
Tokio 快速入门
// Cargo.toml: tokio = { version = "1", features = ["full"] }
#[tokio::main]
async fn main() {
println!("Hello from Tokio!");
// 并发请求
let (a, b) = tokio::join!(
fetch("https://api.example.com/a"),
fetch("https://api.example.com/b"),
);
}
async fn fetch(url: &str) -> String {
reqwest::get(url).await.unwrap().text().await.unwrap()
}
#[tokio::main] 宏展开为:
fn main() {
tokio::runtime::Runtime::new()
.unwrap()
.block_on(async {
// async main 的内容
});
}
任务(Tasks)
// spawn 创建独立任务
let handle = tokio::spawn(async {
// 在运行时线程池上执行
expensive_async_work().await
});
let result = handle.await??; // JoinHandle + Result
常用 API
use tokio::time::{sleep, timeout, interval};
use std::time::Duration;
// 异步睡眠
sleep(Duration::from_secs(1)).await;
// 超时
match timeout(Duration::from_secs(5), long_task()).await {
Ok(result) => println!("完成: {:?}", result),
Err(_) => println!("超时"),
}
// 定时器
let mut interval = interval(Duration::from_secs(1));
loop {
interval.tick().await;
println!("每秒执行一次");
}
select! 竞争
use tokio::select;
async fn race_example() {
select! {
val = async_op1() => println!("op1 先完成: {}", val),
val = async_op2() => println!("op2 先完成: {}", val),
_ = tokio::signal::ctrl_c() => println!("收到 Ctrl+C"),
}
// 未完成的分支被取消(drop)
}
Tokio 同步原语
use tokio::sync::{Mutex, RwLock, Semaphore, mpsc, oneshot, broadcast};
// 异步 Mutex(跨 await 安全)
let lock = Mutex::new(0);
let mut val = lock.lock().await;
*val += 1;
// mpsc channel
let (tx, mut rx) = mpsc::channel(32);
tx.send("hello").await?;
let msg = rx.recv().await;
// oneshot(单次发送)
let (tx, rx) = oneshot::channel();
tx.send("done").unwrap();
let result = rx.await?;
// broadcast(广播)
let (tx, _) = broadcast::channel(16);
let mut rx1 = tx.subscribe();
let mut rx2 = tx.subscribe();
tx.send("to all")?;
// Semaphore(信号量)
let sem = Semaphore::new(3);
let permit = sem.acquire().await?;
// ... 最多 3 个并发
运行时类型
| 运行时 | 特点 | 适用场景 |
|---|---|---|
#[tokio::main] | 多线程(默认) | 服务器、高并发 |
#[tokio::main(flavor = "current_thread")] | 单线程 | 工具脚本、测试 |
| 手动构建 Runtime | 精细控制 | 嵌入式使用 |
常见面试问题
Q1: Tokio 和 async-std 有什么区别?
答案:
| 特性 | Tokio | async-std |
|---|---|---|
| 生态 | 最大,事实标准 | 较小 |
| API 风格 | 自定义 | 模仿 std |
| 运行时 | 可配置 | 全局单例 |
| 维护活跃度 | 非常活跃 | 维护减缓 |
2024 年以后,Tokio 是绝对主流选择。
Q2: CPU 密集任务应该怎么在 Tokio 中处理?
答案:
不要在异步任务中直接运行 CPU 密集计算,会阻塞运行时线程。应使用 tokio::task::spawn_blocking:
let result = tokio::task::spawn_blocking(|| {
// 在专用线程池中执行 CPU 密集计算
heavy_computation()
}).await?;
Q3: #[tokio::main] 创建了多少线程?
答案:
默认创建 CPU 核心数个工作线程。可以手动配置:
#[tokio::main(worker_threads = 4)]
async fn main() { }
Q4: 什么是任务取消(cancellation)?
答案:
在 Rust 中,drop 一个 Future 就是取消它。select! 中未胜出的分支会被 drop。这意味着 async 代码需要考虑在任意 .await 点被取消的情况——确保取消安全性(cancellation safety)。