3 分钟入门 Rust 异步
Rust 异步简介
ust 中的异步操作是通过 Future
trait 来描述的。Future
表示一个还未完成的计算,其核心方法是 poll
。Rust 的异步运行时通过轮询(polling)来判断一个 Future
是否已经完成,轮询的本质是检查任务是否就绪,但并不会阻塞线程。
Rust 异步特点
- 惰性(轮询时获取进展)
- 零成本(无额外内存开销)
- 提供标准,不提供内置运行时
异步组成
- 标准库
- 编辑器
- trait Future:
Future
是 Rust 异步编程的核心概念,代表一个可能还未完成的计算或任务。 - async/await
async
: 用来声明一个异步函数或块,这个函数或块会返回一个实现了Future
trait 的对象。await
: 用来暂停执行直到一个异步操作完成。当Future
准备好时,await
继续执行后面的代
trait Future
pub trait Future {
type Output;
// Required method
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output>;
}
futures
futures 是 Rust 官方提供一个包。它的特点是:
特性/模块 | 描述 |
---|---|
组合子 (Combinators) | 提供用于简化异步操作链式调用的工具,允许对 Future 进行变换、组合和错误处理。 |
StreamExt | Stream 的扩展 trait,提供操作流的组合子,如 next() 、filter() 、map() 、for_each() 。 |
Sink | 代表一个异步写入器,允许异步发送数据到某个目标(如网络、文件等)。 |
futures::future::timeout | 允许为异步任务设置超时时间,确保不会无限等待任务完成。 |
组合与并发执行 | futures crate 提供了便捷的功能组合工具,允许多个异步任务并发执行。 |
执行器
Rust 中有很多执行器,其中就包含大名鼎鼎的的 Tokio
和 async_std
。
Rust 的异步编程使用执行器来运行异步任务。执行器管理任务队列,并不断轮询未完成的任务,直到它们完成。
-
单线程与多线程执行器:执行器可以是单线程的,也可以是多线程的。单线程执行器一次只能轮询一个任务,而多线程执行器可以在多个线程中并发地轮询多个任务。
-
任务的轮询与唤醒:执行器负责唤醒那些未完成但即将就绪的任务,让它们继续执行。
Tokio
#[tokio::main]
async fn main() {
let result = fetch_data().await;
println!("Result: {}", result);
}
async fn fetch_data() -> i32 {
42
}
#[tokio::main]
属性可以直接在 async main 函数中使用 异步函数。使用起来也非常简单。tokio 特点:
模块/功能 | 描述 |
---|---|
tokio::spawn | 启动并发任务,返回 JoinHandle ,用于等待任务完成。 |
tokio::net | 提供异步 TCP/UDP 网络支持,适用于创建高效的服务器和客户端。 |
tokio::fs | 异步文件操作,支持非阻塞的文件读写。 |
tokio::time | 提供异步定时器与时间相关功能,如 sleep 和 timeout 。 |
tokio::sync::mpsc | 提供异步通道,用于任务间通信。多生产者单消费者(mpsc)。 |
tokio::sync::oneshot | 一次性通道,用于发送单个信号或结果。 |
tokio::sync::Mutex | 异步互斥锁,用于保护共享数据,防止并发访问。 |
tokio::sync::RwLock | 异步读写锁,允许多个任务同时读取或一个任务写入。 |
tokio::sync::Semaphore | 信号量,用于控制并发任务的数量。 |
tokio::sync::Notify | 异步通知机制,类似条件变量,允许任务间通知。 |
tokio::time::timeout | 为异步操作设置超时时间,防止任务长时间未完成。 |
tokio_stream::Stream | 异步流,允许按需产生多个值,类似异步迭代器。 |
tokio_stream::StreamExt | Stream 扩展方法,如 for_each 、collect ,用于处理异步流中的数据。 |
tokio::join! | 并行执行多个异步任务,等待它们都完成。 |
tokio::select! | 从多个异步任务中选择最先完成的一个,适合处理多个任务的竞争条件。 |
async_std 异步
async_std 是可移植 Rust 软件的基础,是一组最小且经过实战测试的共享抽象,适用于更广泛的 Rust 生态系统。它提供了 std 类型,例如Future和Stream 、对语言原语的库定义操作、标准宏、 I/O和多线程等等。
- 使用
block_on
:
use async_std::task;
async fn learn_song() -> i32 {
123
}
fn main() {
println!("Hello, world!");
task::block_on(async {
let a = learn_song().await;
println!("{}",a)
})
}
- 使用
async_std::main
属性
#[async_std::main]
async fn main() {
let a = async { 1u8 };
let b = async { 2u8 };
assert_eq!(a.join(b).await, (1u8, 2u8))
}
Pin
和 Unpin
由于异步任务的生命周期不固定,Rust 使用 Pin
来确保异步任务在内存中的位置不会被改变。
Pin
:表示一个固定在内存中的指针,确保异步任务在未完成时不会被移动,因为移动可能会导致不安全行为(比如悬垂指针)。Unpin
:标记某个类型可以安全地在内存中移动。大多数类型默认是Unpin
,但自引用类型(如生成器和Future
)需要Pin
来保持其内存位置稳定。
async fn example() {
let a = String::from("hello");
let future = async {
println!("{}", a); // 引用了局部变量 `a`
};
future.await;
}
小结
Rust 的异步编程通过 Future
trait 实现惰性执行,它使用 async
/await
语法来简化异步代码的编写。异步任务由执行器(如 Tokio
或 async-std
)调度并轮询,确保任务在非阻塞的环境下执行。Rust 的设计确保异步操作具有零成本抽象,同时通过 Pin
和 Unpin
机制,确保异步任务在未完成时不会被移动,避免内存安全问题。这种高效的并发模型,使 Rust 能够处理大量异步任务,同时保持性能和安全性。