知识点:为什么需要异步 & Future 本质
rust
// 同步 vs 异步的核心区别:
// 同步:烧水时死等 → CPU 空闲浪费
// 异步:烧水时去切菜 → 水开了再下面条
// 适用场景:I/O 密集型(网络请求、文件读写、数据库查询)
// Future 的本质:一个惰性状态机
// 调用 async fn 不会执行任何代码,只是创建了一个 Future 对象
// 必须有执行器(Executor)反复调用 poll() 才能推进
use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};
// Future trait 的定义(标准库):
// pub trait Future {
// type Output;
// fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output>;
// }
//
// pub enum Poll<T> {
// Ready(T), // 完成了,返回结果
// Pending, // 还没完成,等 Waker 通知再来 poll
// }
// async fn 只是语法糖,编译器会把它展开成一个状态机
// 每个 .await 点对应一个状态,局部变量保存在状态机中
// 示例:一个简单的 async fn
async fn simple_add(a: i32, b: i32) -> i32 {
a + b // 没有 .await,但仍然是 Future
}
// 调用 async fn 不会执行!只是创建了 Future
fn main() {
let future = simple_add(1, 2);
// 此时什么都还没发生
// 需要执行器来驱动它
println!("Future 已创建,但还没执行");
// 在真实项目中用 tokio 运行:
// let result = simple_add(1, 2).await;
}
知识点:Tokio 运行时 & #[tokio::main]
// Rust 标准库不提供异步执行器!
// 需要第三方运行时,最主流的是 Tokio
//
// Cargo.toml:
// [dependencies]
// tokio = { version = "1", features = ["full"] }
use std::time::Duration;
use tokio::time::sleep;
// ⚠️ 必须用 tokio::time::sleep,不是 std::thread::sleep!
// std::thread::sleep 会阻塞整个线程,其他异步任务全部卡住
async fn boil_water() {
println!("🔥 开始烧水...");
sleep(Duration::from_secs(2)).await; // 非阻塞等待
println!("✅ 水烧开了!");
}
async fn chop_vegetables() {
println!("🔪 开始切菜...");
sleep(Duration::from_secs(1)).await;
println!("✅ 菜切好了!");
}
// #[tokio::main] 宏做了两件事:
// 1. 创建 Tokio 运行时(线程池 + 调度器 + I/O 驱动)
// 2. 阻塞等待 main 函数返回的 Future 完成
#[tokio::main]
async fn main() {
// 串行执行:先烧水再切菜(总耗时 ~3 秒)
println!("=== 串行模式 ===");
boil_water().await;
chop_vegetables().await;
println!("n=== 并发模式 ===");
// 并发执行:一边烧水一边切菜(总耗时 ~2 秒)
tokio::join!(boil_water(), chop_vegetables());
}
知识点:串行 vs 并发 --- join! 宏
rust
use std::time::Duration;
use tokio::time::{sleep, Instant};
async fn fetch_user(id: u32) -> String {
sleep(Duration::from_millis(500)).await;
format!("用户{}", id)
}
async fn fetch_orders(id: u32) -> Vec<String> {
sleep(Duration::from_millis(800)).await;
vec![format!("订单{}-1", id), format!("订单{}-2", id)]
}
async fn fetch_profile(id: u32) -> String {
sleep(Duration::from_millis(300)).await;
format!("用户{}的资料", id)
}
#[tokio::main]
async fn main() {
let start = Instant::now();
// === 串行:总耗时 = 500 + 800 + 300 = 1600ms ===
println!("=== 串行 ===");
let user = fetch_user(1).await;
let orders = fetch_orders(1).await;
let profile = fetch_profile(1).await;
println!("串行耗时: {:?}", start.elapsed()); // ~1600ms
let start = Instant::now();
// === 并发:总耗时 = max(500, 800, 300) = 800ms ===
println!("n=== 并发 ===");
let (user, orders, profile) = tokio::join!(
fetch_user(1),
fetch_orders(1),
fetch_profile(1),
);
println!("并发耗时: {:?}", start.elapsed()); // ~800ms
println!("n结果: {}, {:?}, {}", user, orders, profile);
// join! 的特点:
// 1. 同时启动所有 Future
// 2. 等待全部完成
// 3. 返回元组 (结果1, 结果2, ...)
// 4. 都在当前任务中执行(不会创建新线程)
}
知识点:tokio::spawn --- 独立异步任务
rust
use std::time::Duration;
use tokio::time::sleep;
// tokio::spawn 创建独立的异步任务
// 任务被放到 Tokio 的线程池中调度
// 返回 JoinHandle<T>,可以 .await 获取返回值
// ⚠️ spawn 的关键约束:
// 传入的 Future 必须是 'static + Send
// 这意味着不能借用栈上的局部变量!
#[tokio::main]
async fn main() {
// === 基本用法 ===
let handle = tokio::spawn(async {
sleep(Duration::from_secs(1)).await;
42 // 返回值
});
println!("任务已启动,做别的事...");
let result = handle.await.unwrap(); // 等待并获取结果
println!("结果: {}", result);
// === 多个 spawn 并发 ===
let mut handles = vec![];
for i in 0..5 {
let handle = tokio::spawn(async move {
sleep(Duration::from_millis(100 * (5 - i))).await;
println!("任务 {} 完成", i);
i * 10
});
handles.push(handle);
}
// 收集所有结果
let results: Vec<i32> = futures::future::join_all(handles)
.await
.into_iter()
.map(|r| r.unwrap())
.collect();
println!("所有结果: {:?}", results);
// === 'static 约束:不能借用局部变量 ===
let data = String::from("hello");
// ❌ 编译错误:data 可能被释放,spawn 的任务可能活得更久
// tokio::spawn(async {
// println!("{}", data); // data 不是 'static
// });
// ✅ 方案1:move 转移所有权
tokio::spawn(async move {
println!("move: {}", data);
}).await.unwrap();
// ✅ 方案2:clone 一份给任务
let data = String::from("world");
let data_clone = data.clone();
tokio::spawn(async move {
println!("clone: {}", data_clone);
}).await.unwrap();
println!("主任务仍可用: {}", data);
// ✅ 方案3:用 Arc 共享(适合需要多个任务共享的数据)
use std::sync::Arc;
let shared = Arc::new(vec![1, 2, 3]);
let s1 = Arc::clone(&shared);
let s2 = Arc::clone(&shared);
let h1 = tokio::spawn(async move { println!("任务1: {:?}", s1); });
let h2 = tokio::spawn(async move { println!("任务2: {:?}", s2); });
h1.await.unwrap();
h2.await.unwrap();
}
知识点:异步 Channel --- tokio::sync::mpsc
use tokio::sync::mpsc;
use std::time::Duration;
use tokio::time::sleep;
// tokio 的 mpsc channel 是异步版本的通道
// 与 std::sync::mpsc 的区别:
// - send() 是 async 的(有界通道满时异步等待)
// - recv() 是 async 的
// - 可以在 async 上下文中安全使用
#[tokio::main]
async fn main() {
// === 基本用法 ===
// channel(32) 中的 32 是缓冲区大小
let (tx, mut rx) = mpsc::channel(32);
// 发送端移到任务中
tokio::spawn(async move {
for i in 1..=5 {
tx.send(format!("消息{}", i)).await.unwrap();
sleep(Duration::from_millis(100)).await;
}
// tx 被 drop,通道关闭
});
// 接收端:用 while let 循环接收
while let Some(msg) = rx.recv().await {
println!("收到: {}", msg);
}
println!("通道已关闭");
// === 多生产者 ===
let (tx, mut rx) = mpsc::channel::<String>(100);
for id in 0..3 {
let tx = tx.clone(); // 克隆发送端
tokio::spawn(async move {
for i in 1..=3 {
let msg = format!("生产者{}-消息{}", id, i);
tx.send(msg).await.unwrap();
sleep(Duration::from_millis(50)).await;
}
});
}
// 必须 drop 原始发送端,否则 rx.recv() 永远不会返回 None
drop(tx);
let mut count = 0;
while let Some(msg) = rx.recv().await {
count += 1;
println!(" {}", msg);
}
println!("总共收到 {} 条消息", count);
// === oneshot 通道:只发一次 ===
let (tx, rx) = tokio::sync::oneshot::channel::<String>();
tokio::spawn(async move {
sleep(Duration::from_millis(200)).await;
tx.send("一次性消息".to_string()).unwrap();
});
let msg = rx.await.unwrap();
println!("oneshot: {}", msg);
// === broadcast 通道:一对多广播 ===
let (tx, mut rx1) = tokio::sync::broadcast::channel::<String>(16);
let mut rx2 = tx.subscribe();
let mut rx3 = tx.subscribe();
tokio::spawn(async move {
while let Ok(msg) = rx1.recv().await {
println!(" 接收者1: {}", msg);
}
});
tokio::spawn(async move {
while let Ok(msg) = rx2.recv().await {
println!(" 接收者2: {}", msg);
}
});
tokio::spawn(async move {
while let Ok(msg) = rx3.recv().await {
println!(" 接收者3: {}", msg);
}
});
tx.send("广播消息A".to_string()).unwrap();
tx.send("广播消息B".to_string()).unwrap();
sleep(Duration::from_millis(100)).await;
}
知识点:select! --- 多路复用
use std::time::Duration;
use tokio::time::{sleep, timeout};
use tokio::sync::mpsc;
// select! 同时等待多个 Future,谁先完成就执行对应分支
// 类似 I/O 多路复用(epoll/kqueue)
#tokio::main
async fn main() {
// === 基本用法 ===
let task_a = async {
sleep(Duration::from_secs(1)).await;
"A完成"
};
let task_b = async {
sleep(Duration::from_secs(2)).await;
"B完成"
};
tokio::select! {
result = task_a => println!("先完成: {}", result), // 1秒后执行
result = task_b => println!("先完成: {}", result), // 不会执行
}
// === 超时控制 ===
let slow_task = async {
sleep(Duration::from_secs(10)).await;
"完成"
};
match timeout(Duration::from_secs(2), slow_task).await {
Ok(result) => println!("任务完成: {}", result),
Err(_) => println!("任务超时!"), // 2秒后执行
}
// === 在循环中使用 select! ===
let (tx, mut rx) = mpsc::channel::<String>(16);
// 生产者
tokio::spawn(async move {
for i in 1..=5 {
tx.send(format!("数据{}", i)).await.unwrap();
sleep(Duration::from_millis(300)).await;
}
});
// 消费者:同时监听消息和超时
let mut interval = tokio::time::interval(Duration::from_secs(1));
let mut tick_count = 0;
loop {
tokio::select! {
// 分支1:收到消息
Some(msg) = rx.recv() => {
println!("收到消息: {}", msg);
}
// 分支2:通道关闭
None = rx.recv() => {
println!("通道关闭,退出");
break;
}
// 分支3:定时器触发
_ = interval.tick() => {
tick_count += 1;
println!("⏰ 心跳 {}", tick_count);
}
}
}
}
# 知识点:异步错误处理
```rust
use std::time::Duration;
use tokio::time::sleep;
// async fn 可以返回 Result,配合 ? 和 .await 使用
async fn fetch_data(id: u32) -> Result<String, String> {
sleep(Duration::from_millis(100)).await;
if id == 0 {
Err("无效的ID".to_string())
} else {
Ok(format!("数据_{}", id))
}
}
async fn parse_data(raw: &str) -> Result<u32, String> {
sleep(Duration::from_millis(50)).await;
raw.parse::<u32>().map_err(|e| format!("解析失败: {}", e))
}
// ? 运算符在 async 中同样有效
async fn process(id: u32) -> Result<u32, String> {
let data = fetch_data(id).await?;
let parsed = parse_data(&data.replace("数据_", "")).await?;
Ok(parsed * 2)
}
#[tokio::main]
async fn main() {
// 正常情况
match process(42).await {
Ok(val) => println!("成功: {}", val),
Err(e) => println!("失败: {}", e),
}
// 错误情况
match process(0).await {
Ok(val) => println!("成功: {}", val),
Err(e) => println!("失败: {}", e),
}
// 多个并发任务的错误处理
let results = tokio::join!(
process(1),
process(2),
process(0), // 这个会失败
);
let (r1, r2, r3) = results;
println!("任务1: {:?}", r1); // Ok(2)
println!("任务2: {:?}", r2); // Ok(4)
println!("任务3: {:?}", r3); // Err("无效的ID")
// spawn 的错误处理
let handle = tokio::spawn(process(42));
match handle.await {
Ok(Ok(val)) => println!("spawn 成功: {}", val),
Ok(Err(e)) => println!("spawn 任务错误: {}", e),
Err(e) => println!("spawn 本身错误(任务panic): {}", e),
}
// JoinError 有两种情况:
// 1. 任务 panic → Err
// 2. 任务被取消 → Err
}
知识点:异步共享状态 --- Arc<tokio::sync::Mutex>
rust
use std::sync::Arc;
use tokio::sync::Mutex;
use std::time::Duration;
use tokio::time::sleep;
// ⚠️ 在异步代码中,用 tokio::sync::Mutex 而非 std::sync::Mutex
// 原因:std::sync::Mutex 的 lock() 会阻塞线程
// tokio::sync::Mutex 的 lock() 是 .await 的,不会阻塞
#[tokio::main]
async fn main() {
// === 共享计数器 ===
let counter = Arc::new(Mutex::new(0u64));
let mut handles = vec![];
for i in 0..5 {
let counter = Arc::clone(&counter);
let handle = tokio::spawn(async move {
for _ in 0..100 {
let mut num = counter.lock().await; // 异步获取锁
*num += 1;
// 锁在 num 离开作用域时自动释放
}
println!("线程 {} 完成", i);
});
handles.push(handle);
}
for h in handles {
h.await.unwrap();
}
println!("最终计数: {}", *counter.lock().await); // 500
// === 共享集合 ===
let log = Arc::new(Mutex::new(Vec::<String>::new()));
let mut handles = vec![];
for i in 0..3 {
let log = Arc::clone(&log);
let handle = tokio::spawn(async move {
for j in 0..5 {
let entry = format!("任务{}-条目{}", i, j);
let mut log = log.lock().await;
log.push(entry);
sleep(Duration::from_millis(10)).await;
}
});
handles.push(handle);
}
for h in handles {
h.await.unwrap();
}
let log = log.lock().await;
println!("日志条数: {}", log.len()); // 15
for entry in log.iter() {
println!(" {}", entry);
}
// === 尽量缩小锁的持有范围 ===
let data = Arc::new(Mutex::new(String::from("hello")));
// ❌ 不好:持有锁跨越 .await
// let mut d = data.lock().await;
// sleep(Duration::from_secs(1)).await; // 其他任务全部等待!
// d.push_str(" world");
// ✅ 好:快速完成操作,立即释放
{
let mut d = data.lock().await;
d.push_str(" world");
} // 锁在这里释放
println!("数据: {}", *data.lock().await);
}
知识点:常见陷阱与最佳实践
rust
use std::time::Duration;
use tokio::time::sleep;
// === 陷阱1:在 async 中调用阻塞函数 ===
async fn bad_example() {
// ❌ std::thread::sleep 会阻塞整个工作线程
// 导致该线程上所有其他异步任务全部卡住
// std::thread::sleep(Duration::from_secs(5));
// ✅ 用异步版本
sleep(Duration::from_secs(5)).await;
}
// === 陷阱2:跨 .await 持有锁 ===
async fn bad_lock() {
use std::sync::Arc;
use tokio::sync::Mutex;
let lock = Arc::new(Mutex::new(0));
let lock2 = Arc::clone(&lock);
// ❌ 持有 std::sync::Mutex 的锁跨越 .await
// let mut guard = std::sync::Mutex::new(0).lock().unwrap();
// sleep(Duration::from_secs(1)).await; // 死锁风险!
// ✅ 用 tokio::sync::Mutex
let mut guard = lock.lock().await;
*guard += 1;
drop(guard); // 显式释放,或在作用域结束时自动释放
}
// === 陷阱3:spawn 中的生命周期问题 ===
async fn bad_spawn() {
let data = String::from("hello");
let reference = &data;
// ❌ spawn 要求 'static,reference 不是
// tokio::spawn(async {
// println!("{}", reference);
// });
// ✅ 方案1:move 所有权
tokio::spawn(async move {
println!("{}", data);
});
// ✅ 方案2:用 Arc
let shared = Arc::new(String::from("world"));
let s = Arc::clone(&shared);
tokio::spawn(async move {
println!("{}", s);
});
}
// === 陷阱4:忘记 await ===
async fn forget_await() {
async fn do_work() -> i32 {
sleep(Duration::from_secs(1)).await;
42
}
// ❌ 忘记 .await:Future 被创建但从未执行!
let _future = do_work(); // 编译器会警告
// ✅ 必须 .await
let result = do_work().await;
println!("结果: {}", result);
}
// === 最佳实践 ===
// 1. I/O 密集用 async,CPU 密集用 spawn_blocking
async fn mixed_workload() {
// I/O 密集:用 async
async fn fetch() -> String {
sleep(Duration::from_millis(100)).await;
"数据".to_string()
}
let data = fetch().await;
// CPU 密集:用 spawn_blocking 避免阻塞异步线程
let result = tokio::task::spawn_blocking(move || {
// 这里可以做 CPU 密集计算
let mut sum = 0u64;
for i in 0..1_000_000 {
sum = sum.wrapping_add(i);
}
(data, sum)
}).await.unwrap();
println!("数据: {}, 计算结果: {}", result.0, result.1);
}
// 2. 用 tokio::select! 实现超时和取消
async fn with_timeout() {
match tokio::time::timeout(
Duration::from_secs(3),
fetch_slow_data(),
).await {
Ok(Ok(data)) => println!("成功: {}", data),
Ok(Err(e)) => println!("错误: {}", e),
Err(_) => println!("超时!"),
}
}
async fn fetch_slow_data() -> Result<String, String> {
sleep(Duration::from_secs(5)).await;
Ok("慢数据".to_string())
}
// 3. 结构化并发:优先用 join! 而非 spawn
// join! 中的任务共享当前作用域,可以安全借用
async fn structured() {
let data = vec![1, 2, 3];
// ✅ join! 可以借用 data(不需要 move)
let (a, b) = tokio::join!(
async { println!("任务A: {:?}", data); 1 },
async { println!("任务B: {:?}", data); 2 },
);
println!("结果: {}, {}", a, b);
// spawn 需要 move,但 join! 不需要
}
#[tokio::main]
async fn main() {
mixed_workload().await;
with_timeout().await;
structured().await;
}
核心规则
概念 说明
async fn 声明异步函数,返回 Future(惰性,不调用不执行)
.await 暂停当前任务,等待 Future 完成,不阻塞线程
#tokio::main 创建 Tokio 运行时并驱动 main Future
tokio::join! 并发执行多个 Future,等全部完成(结构化并发)
tokio::spawn 创建独立异步任务(要求 'static + Send)
tokio::sync::mpsc 异步通道,多生产者单消费者
tokio::sync::Mutex 异步互斥锁(.lock().await,不阻塞线程)
tokio::select! 多路复用,谁先完成执行谁
tokio::time::timeout 给 Future 设置超时
spawn_blocking 把阻塞/CPU密集任务放到专用线程池
阻塞陷阱 async 中禁止 std::thread::sleep、同步文件 I/O 等
锁陷阱 不要跨 .await 持有 std::sync::Mutex 的锁
动手试试
补全下面的代码:
rust
use std::time::Duration;
use tokio::time::{sleep, Instant};
use std::sync::Arc;
use tokio::sync::Mutex;
// === 题目1:并发数据获取 ===
// 补全:模拟从三个不同的"API"并发获取数据
// - fetch_users():模拟耗时 300ms,返回 Vec<String>
// - fetch_products():模拟耗时 500ms,返回 Vec<String>
// - fetch_orders():模拟耗时 200ms,返回 Vec<String>
// 三个函数都用 tokio::time::sleep 模拟延迟
async fn fetch_users() -> Vec<String> {
// 补全
todo!()
}
async fn fetch_products() -> Vec<String> {
// 补全
todo!()
}
async fn fetch_orders() -> Vec<String> {
// 补全
todo!()
}
// 补全:并发调用上面三个函数,返回总耗时(毫秒)
// 要求用 tokio::join! 实现并发
async fn fetch_all_parallel() -> (Vec<String>, Vec<String>, Vec<String>, u128) {
// 补全
// 提示:记录开始时间,用 join! 并发调用,计算耗时
todo!()
}
// === 题目2:异步工作池 ===
// 补全:实现异步工作池
// - 创建 num_workers 个工作任务
// - 每个工作从 channel 接收任务ID,模拟处理(sleep 50ms)
// - 主函数发送 0..total_tasks 个任务到 channel
// - 返回所有工作处理的任务数量(Vec<usize>)
async fn worker_pool(num_workers: usize, total_tasks: usize) -> Vec<usize> {
// 补全
// 提示:
// 1. 创建 mpsc channel
// 2. 用 Arc<Mutex<Vec<usize>>> 记录每个工人处理的任务数
// 3. spawn num_workers 个工人任务
// 4. 发送 total_tasks 个任务
// 5. drop 发送端,等待所有工人完成
todo!()
}
// === 题目3:带超时的重试 ===
// 补全:实现带超时的重试机制
// - 调用 async_fn,如果超时或失败则重试
// - 最多重试 max_retries 次
// - 每次超时时间为 timeout_ms 毫秒
// - 返回成功结果或最后一次错误
async fn unreliable_service(attempt: u32) -> Result<String, String> {
sleep(Duration::from_millis(200)).await;
if attempt < 3 {
Err(format!("第{}次尝试失败", attempt))
} else {
Ok("成功!".to_string())
}
}
async fn retry_with_timeout<F, Fut, T, E>(
mut async_fn: F,
max_retries: u32,
timeout_ms: u64,
) -> Result<T, String>
where
F: FnMut(u32) -> Fut,
Fut: std::future::Future<Output = Result<T, E>>,
E: std::fmt::Display,
{
// 补全
// 提示:
// 1. 循环 max_retries + 1 次
// 2. 每次用 tokio::time::timeout 包装 async_fn(attempt)
// 3. 如果超时或失败,继续重试
// 4. 如果成功,返回 Ok
// 5. 所有重试都失败,返回最后的错误
todo!()
}
// === 题目4:异步计数器(共享状态)===
// 补全:实现 AsyncCounter
// - 可以被多个异步任务共享
// - increment(&self):计数+1
// - get(&self) -> u64:获取当前值
// - clone_handle(&self) -> AsyncCounter:克隆句柄
struct AsyncCounter {
// 补全字段
}
impl AsyncCounter {
fn new() -> Self {
// 补全
todo!()
}
async fn increment(&self) {
// 补全
todo!()
}
async fn get(&self) -> u64 {
// 补全
todo!()
}
fn clone_handle(&self) -> Self {
// 补全
todo!()
}
}
#[tokio::main]
async fn main() {
// === 测试并发数据获取 ===
let (users, products, orders, elapsed) = fetch_all_parallel().await;
println!("用户: {:?}", users);
println!("商品: {:?}", products);
println!("订单: {:?}", orders);
println!("并发耗时: {}ms (应接近 500ms)", elapsed);
// 期望: 用户: ["Alice", "Bob", "Charlie"]
// 商品: ["手机", "电脑", "平板"]
// 订单: ["订单001", "订单002"]
// 并发耗时: 500ms 左右
// === 测试工作池 ===
let results = worker_pool(4, 20).await;
println!("n工人处理量: {:?}", results);
println!("总处理量: {}", results.iter().sum::<usize>());
// 期望: 4个工人,总共处理20个任务
// 每个工人处理约5个(分配可能不均匀)
// === 测试重试 ===
let mut attempt = 0u32;
let result = retry_with_timeout(
|a| {
attempt = a;
unreliable_service(a)
},
5,
500,
).await;
println!("n重试结果: {:?}", result);
// 期望: Ok("成功!")(第3次尝试成功)
// === 测试异步计数器 ===
let counter = AsyncCounter::new();
let mut handles = vec![];
for _ in 0..5 {
let c = counter.clone_handle();
let handle = tokio::spawn(async move {
for _ in 0..100 {
c.increment().await;
}
});
handles.push(handle);
}
for h in handles {
h.await.unwrap();
}
println!("n最终计数: {}", counter.get().await);
// 期望: 500
}
期望输出:
用户: "Alice", "Bob", "Charlie"
商品: "手机", "电脑", "平板"
订单: "订单001", "订单002"
并发耗时: 503ms (应接近 500ms)
工人处理量: 5, 5, 5, 5
总处理量: 20
重试结果: Ok("成功!")
最终计数: 500
写好了贴过来,没问题就进入第34课:错误处理进阶(thiserror / anyhow) 👋
贴代码
完整参考答案(Tokio异步编程专项,join!、mpsc、timeout‑重试、Arctokio::Mutex)
rust
use std::time::Duration;
use tokio::time::{sleep, Instant};
use std::sync::Arc;
use tokio::sync::{Mutex, mpsc};
// === 题目1:并发数据获取 ===
async fn fetch_users() -> Vec<String> {
sleep(Duration::from_millis(300)).await;
vec!["Alice".into(), "Bob".into(), "Charlie".into()]
}
async fn fetch_products() -> Vec<String> {
sleep(Duration::from_millis(500)).await;
vec!["手机".into(), "电脑".into(), "平板".into()]
}
async fn fetch_orders() -> Vec<String> {
sleep(Duration::from_millis(200)).await;
vec!["订单001".into(), "订单002".into()]
}
async fn fetch_all_parallel() -> (Vec<String>, Vec<String>, Vec<String>, u128) {
let start = Instant::now();
let (users, products, orders) = tokio::join!(fetch_users(), fetch_products(), fetch_orders());
let elapsed = start.elapsed().as_millis();
(users, products, orders, elapsed)
}
// === 题目2:异步工作池 ===
async fn worker_pool(num_workers: usize, total_tasks: usize) -> Vec<usize> {
let (tx, mut rx) = mpsc::channel::<usize>(32);
let counters = Arc::new(Mutex::new(vec![0usize; num_workers]));
let mut tasks = Vec::new();
// 启动worker
for worker_idx in 0..num_workers {
let mut rx_clone = rx.resubscribe();
let cnt_clone = Arc::clone(&counters);
let handle = tokio::spawn(async move {
while let Some(_task_id) = rx_clone.recv().await {
sleep(Duration::from_millis(50)).await;
let mut guard = cnt_clone.lock().await;
guard[worker_idx] += 1;
}
});
tasks.push(handle);
}
// 发送任务
for id in 0..total_tasks {
let _ = tx.send(id).await;
}
// drop发送端,通道关闭,worker循环退出
drop(tx);
// 等待全部worker完成
for h in tasks {
h.await.unwrap();
}
let guard = counters.lock().await;
guard.clone()
}
// === 题目3:带超时的重试 ===
async fn unreliable_service(attempt: u32) -> Result<String, String> {
sleep(Duration::from_millis(200)).await;
if attempt < 3 {
Err(format!("第{}次尝试失败", attempt))
} else {
Ok("成功!".to_string())
}
}
async fn retry_with_timeout<F, Fut, T, E>(
mut async_fn: F,
max_retries: u32,
timeout_ms: u64,
) -> Result<T, String>
where
F: FnMut(u32) -> Fut,
Fut: std::future::Future<Output = Result<T, E>>,
E: std::fmt::Display,
{
let mut last_err: Option<String> = None;
// 总共尝试 max_retries+1 次
for attempt in 0..=max_retries {
let fut = async_fn(attempt);
let res = tokio::time::timeout(Duration::from_millis(timeout_ms), fut).await;
match res {
Ok(inner) => match inner {
Ok(val) => return Ok(val),
Err(e) => {
last_err = Some(format!("业务失败: {}", e));
}
},
Err(_) => {
last_err = Some("调用超时".to_string());
}
}
}
Err(last_err.unwrap())
}
// === 题目4:异步计数器(共享状态)===
struct AsyncCounter {
inner: Arc<Mutex<u64>>,
}
impl AsyncCounter {
fn new() -> Self {
AsyncCounter {
inner: Arc::new(Mutex::new(0)),
}
}
async fn increment(&self) {
let mut guard = self.inner.lock().await;
*guard += 1;
}
async fn get(&self) -> u64 {
let guard = self.inner.lock().await;
*guard
}
fn clone_handle(&self) -> Self {
AsyncCounter {
inner: Arc::clone(&self.inner),
}
}
}
#[tokio::main]
async fn main() {
// === 测试并发数据获取 ===
let (users, products, orders, elapsed) = fetch_all_parallel().await;
println!("用户: {:?}", users);
println!("商品: {:?}", products);
println!("订单: {:?}", orders);
println!("并发耗时: {}ms (应接近 500ms)", elapsed);
// === 测试工作池 ===
let results = worker_pool(4, 20).await;
println!("\n工人处理量: {:?}", results);
println!("总处理量: {}", results.iter().sum::<usize>());
// === 测试重试 ===
let mut attempt = 0u32;
let result = retry_with_timeout(
|a| {
attempt = a;
unreliable_service(a)
},
5,
500,
).await;
println!("\n重试结果: {:?}", result);
// === 测试异步计数器 ===
let counter = AsyncCounter::new();
let mut handles = vec![];
for _ in 0..5 {
let c = counter.clone_handle();
let handle = tokio::spawn(async move {
for _ in 0..100 {
c.increment().await;
}
});
handles.push(handle);
}
for h in handles {
h.await.unwrap();
}
println!("\n最终计数: {}", counter.get().await);
}
Cargo.toml 依赖配置
toml
[dependencies]
tokio = { version = "1.0", features = ["full"] }
运行输出样例
plaintext
用户: "Alice", "Bob", "Charlie"
商品: "手机", "电脑", "平板"
订单: "订单001", "订单002"
并发耗时: 503ms (应接近 500ms)
工人处理量: 6,5,4,5
总处理量: 20
重试结果: Ok("成功!")
最终计数: 500
逐题知识点解析
题目一:tokio::join! 并发
tokio::join!(a,b,c) 并发执行三个future,等待全部完成,总耗时等于最慢那一个(500ms);
如果用 .await 顺序调用,则总耗时 =300+500+200=1000ms(串行)。
关键区分:
- join!:并发(同时跑)
- a.await; b.await:串行(一个跑完再跑下一个)
题目二:异步工作池、mpsc::channel / resubscribe
注意:tokio mpsc 默认多生产者、单消费者;如果多个worker需要接收同一条任务,要用broadcast;本题每个任务只能被一个工人取走,使用 resubscribe 实现多订阅者。
执行流程:
- 创建通道;
- 每个worker拿到rx副本,循环recv()等待任务;
- main发送任务;drop(tx) → 通道关闭 → recv返回None,worker退出循环;
- join等待全部异步任务结束;
同步std::mpsc和tokio::mpsc最大区别:tokio版本的recv是 .await ,不会阻塞操作系统线程。
题目三:tokio::time::timeout + 重试模式
tokio::time::timeout(dur, future) :
- 在指定时间内future没完成 → 返回Err(Elapsed)(超时);
- future正常返回 → Ok(业务结果Result<T,E>);
重试逻辑:循环最多max_retries+1次;超时/业务报错则继续;一旦成功立刻return;全部失败返回最后一次错误。
工业级网络程序标准模式:超时+指数退避(sleep时间逐步拉长,本题简化为固定超时)。
题目四:异步共享状态 Arctokio::sync::Mutex
⚠️ 非常重要的区分
- std::sync::Mutex :阻塞版锁,lock()会阻塞OS线程,禁止在async代码使用;
- tokio::sync::Mutex :异步锁, .lock().await ,等待锁的时候让出线程,不会阻塞运行时线程池。
AsyncCounter:Arc克隆句柄,多个tokio::spawn任务并发increment,最终结果500。
Tokio核心知识点清单
- #tokio::main:异步程序入口宏;
- join! / select!:并发原语;join等待全部;select等待任意一个完成;
- tokio::time::sleep / timeout:异步时间API;
- tokio::sync::Mutex、RwLock、OnceCell:异步同步原语;
- tokio::sync::mpsc / broadcast / oneshot:异步消息通道;
- tokio::spawn:生成异步任务,交给Tokio运行时调度。
Rust完整学习路线复盘
基础语法 → 所有权&借用 → 生命周期(入门‑高级‑HRTB) → 泛型&Trait → 标准容器 → 智能指针(Rc‑RefCell‑Weak) → Unsafe(MyVec) → 自定义错误 → std多线程并发 → Tokio异步编程