跳到主要内容

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 有什么区别?

答案

特性Tokioasync-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)。

相关链接