【Rust入门知识点学与练】第33课:异步编程入门(async/await)强化

知识点:为什么需要异步 & 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 实现多订阅者。

执行流程:

  1. 创建通道;
  2. 每个worker拿到rx副本,循环recv()等待任务;
  3. main发送任务;drop(tx) → 通道关闭 → recv返回None,worker退出循环;
  4. 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

⚠️ 非常重要的区分

  1. std::sync::Mutex :阻塞版锁,lock()会阻塞OS线程,禁止在async代码使用;
  2. tokio::sync::Mutex :异步锁, .lock().await ,等待锁的时候让出线程,不会阻塞运行时线程池。

AsyncCounter:Arc克隆句柄,多个tokio::spawn任务并发increment,最终结果500。

Tokio核心知识点清单

  1. #tokio::main:异步程序入口宏;
  2. join! / select!:并发原语;join等待全部;select等待任意一个完成;
  3. tokio::time::sleep / timeout:异步时间API;
  4. tokio::sync::Mutex、RwLock、OnceCell:异步同步原语;
  5. tokio::sync::mpsc / broadcast / oneshot:异步消息通道;
  6. tokio::spawn:生成异步任务,交给Tokio运行时调度。

Rust完整学习路线复盘

基础语法 → 所有权&借用 → 生命周期(入门‑高级‑HRTB) → 泛型&Trait → 标准容器 → 智能指针(Rc‑RefCell‑Weak) → Unsafe(MyVec) → 自定义错误 → std多线程并发 → Tokio异步编程

相关推荐
传奇开心果编程1 小时前
【Rust入门知识点学与练】第34课:所有权与借用
开发语言·学习·rust
柒儿吖1 小时前
SSCom 重构全记录:用 Rust 把串口调试助手送上鸿蒙 PC、macOS、Windows和Linux
测试工具·rust·harmonyos
AAA代码批发商1 小时前
DAY43 ARM 体系结构学习笔记|嵌入式入门,从硬件组件到流水线、内存与栈帧
arm开发·笔记·学习
彧azz2 小时前
初学Unity:编辑器
笔记·学习·unity·游戏引擎
529宝宝起名网2 小时前
用 Python 开发历史名字查询与起名灵感工具:从古籍人物数据库到名字文化故事生成
开发语言·前端·python
明德扬2 小时前
AD9653单通道波形怎么看?幅度、频率与基础测量入门
学习·fpga开发·fpga
幼儿园蛋小黄2 小时前
学习ARM
学习
ocean21032 小时前
2025-2026年C++专题大厂面试高频问题
开发语言·c++·面试
白远山2 小时前
健身场馆无人自动化系统实战指南:从架构设计到部署全流程解析
java·开发语言·架构·需求分析