Rust异步编程
当一个线程被I/O阻塞,只能等系统I/O调用执行完成后,这段时间存在资源浪费。
例如处理n多个客户端请求,对于每个请求创建一个线程,而每个线程的栈可能有上千字节,在线程等待期间,就会有上千字节内存被占用,但是又不做任何其他事情,当请求的数量巨大时,内存的占用就非常明显了。
以下内容是AI生成的解释
一、Rust 异步编程核心概念
1.1 异步编程的本质
Rust 的异步编程基于 零成本抽象 的理念,在编译期将 async/await 语法转换为状态机,避免了运行时的额外开销。与 Go 的 goroutine 或 Java 的虚拟线程不同,Rust 的异步模型是 惰性求值(lazy) 的——创建一个 Future 并不会立即执行,需要 executor 来驱动它。
1.2 核心三要素
1 | ┌─────────────────────────────────────────────┐ |
1.3 Future Trait
Future 是 Rust 异步编程的基石:
1 | pub trait Future { |
工作流程:
- Executor 调用
Future::poll()尝试推进任务 - 如果返回
Poll::Ready(value),任务完成 - 如果返回
Poll::Pending,Future 通过Waker注册回调 - 当外部事件就绪时(如 I/O 完成),
Waker::wake()通知 Executor 重新 poll
1.4 async/await 的编译期转换
1 | // 你写的代码: |
编译器会将其转换为类似如下的状态机:
1 | enum FetchDataState { |
1.5 Pin 与自引用
Pin 的存在是为了解决 自引用结构体 问题。当 async 块跨 await 点持有引用时,编译器生成的状态机会包含自引用字段:
1 | async fn example() { |
Pin<&mut Self> 确保 Future 在内存中不会被移动,从而保证自引用的安全性。
1.6 Waker 机制
Waker 是连接 I/O 事件系统 和 Executor 的桥梁:
1 | ┌──────────┐ poll() ┌──────────┐ |
二、手写一个简单的异步运行时
下面我们从零开始构建一个包含以下组件的异步运行时:
- Task(任务):封装 Future
- Executor(执行器):调度和执行任务
- Spawner(生成器):向 Executor 提交新任务
- 简单的定时器 Future:演示自定义 Future 的实现
- 简单的异步 TCP 请求:演示网络 I/O
完整代码
1 | use std::{ |
Cargo.toml 依赖
1 | [package] |
三、代码架构解析
1 | ┌─────────────────────────────────────────────────────┐ |
执行流程详解
1 | 时间线 ──────────────────────────────────────────────► |
四、关键机制深入解读
4.1 为什么用 mpsc::channel 作为任务队列?
1 | // 生产者-消费者模型完美匹配 Executor 的需求: |
4.2 Waker 的实现原理
Waker 的核心是一个 虚函数表(VTable),这是 Rust 低级抽象的体现:
1 | // RawWakerVTable 定义了 4 个操作: |
4.3 与 tokio 等成熟运行时的对比
| 特性 | 我们的实现 | tokio |
|---|---|---|
| 任务调度 | 单线程,mpsc channel | 多线程 work-stealing |
| I/O 模型 | 后台线程 + 阻塞 I/O | epoll/kqueue/IOCP (非阻塞) |
| 定时器 | 每个定时器一个线程 | 时间轮算法,共享线程 |
| Waker | 手动 VTable | 优化的 waker 实现 |
| 适用场景 | 学习/理解原理 | 生产环境 |
4.4 真正的异步 I/O vs 我们的模拟
1 | 我们的实现(线程模拟异步): |
五、不使用 lazy_static 的改进版本
如果你不想引入外部依赖,可以使用以下改进方案:
1 | use std::sync::OnceLock; |
或者更优雅的方式——将 pending tasks 存储在 Executor 内部,通过 Arc 共享:
1 | struct Executor { |
六、运行效果示例
1 | ╔══════════════════════════════════════════╗ |
七、总结
Rust 异步编程的核心设计哲学
- 零成本抽象:async/await 在编译期转换为状态机,无 GC、无运行时开销
- 运行时可插拔:语言只定义
Futuretrait,具体运行时由库提供(tokio、async-std、smol 等) - 协作式调度:Future 在 await 点主动让出控制权
- 类型安全:Pin 机制在编译期防止自引用问题
- 惰性求值:Future 不 poll 就不执行,避免不必要的计算
何时使用异步
| 场景 | 推荐方式 |
|---|---|
| 大量并发 I/O(Web 服务器、爬虫) | ✅ async/await |
| CPU 密集计算 | ❌ 使用 rayon 或线程池 |
| 少量并发任务 | 🤔 线程可能更简单 |
| 嵌入式/no_std | ✅ async 很适合(embassy 框架) |