引言
大家好!今天我们来聊聊 Rust 中的异步编程。作为一个现代编程语言,Rust 在 2019 年引入了 async/await 特性。这个特性让我们能够更高效地处理并发任务,特别是在需要处理大量网络连接的场景下。让我们一起来看看如何从传统的线程编程过渡到异步编程。
从线程说起
我们先来看一个使用线程的简单示例:
fn foo(n: u64) {
println!("start {n}"); // 打印开始信息
thread::sleep(Duration::from_secs(1)); // 休眠 1 秒
println!("end {n}"); // 打印结束信息
}
fn main() {
let mut thread_handles = Vec::new();
// 创建 10 个线程
for n in 1..=10 {
thread_handles.push(thread::spawn(move || foo(n)));
}
// 等待所有线程完成
for handle in thread_handles {
handle.join().unwrap();
}
}
这段代码可以很好地处理几十个甚至几百个并发任务。但当我们尝试创建成千上万个线程时,就会遇到资源限制的问题。这是因为每个线程都会占用大量内存(默认情况下在 Linux 上是 8MB 的栈空间)。
异步编程的优势
让我们看看如何用异步方式重写上面的代码:
async fn foo(n: u64) {
println!("start {n}"); // 打印开始信息
tokio::time::sleep(Duration::from_secs(1)).await; // 异步休眠 1 秒
println!("end {n}"); // 打印结束信息
}
#[tokio::main]
async fn main() {
let mut futures = Vec::new();
// 创建 10 个异步任务
for n in 1..=10 {
futures.push(foo(n));
}
// 并发执行所有任务
future::join_all(futures).await;
}
使用异步编程的版本不仅代码结构清晰,而且可以轻松处理成千上万的并发任务。在实际测试中,去掉打印语句并使用发布模式编译后,甚至可以同时运行百万级的任务!
常见陷阱
在使用异步编程时,有两个常见的错误需要注意:
在异步函数中使用同步阻塞操作(如 thread::sleep
)会导致性能问题串行等待多个 future 会失去并发的优势
错误示例:
// 错误示例 1:在异步函数中使用同步阻塞
async fn foo_wrong(n: u64) {
println!("start {n}");
thread::sleep(Duration::from_secs(1)); // 错误!应该使用 tokio::time::sleep
println!("end {n}");
}
// 错误示例 2:串行等待 future
async fn main_wrong() {
let mut futures = Vec::new();
for n in 1..=10 {
futures.push(foo(n));
}
for future in futures {
future.await; // 错误!这样会变成串行执行
}
}
总结
Rust 的异步编程提供了比线程更轻量级的并发方案 使用 async/await 可以轻松处理大量并发任务 需要注意避免在异步函数中使用同步阻塞操作 使用 join_all
等工具来并发等待多个 future
异步编程是现代服务器编程的重要工具,掌握它可以帮助我们构建高性能的网络应用。在实际应用中,我们通常会使用 Tokio 这样的异步运行时来处理具体的异步任务调度。
参考文章
Async Rust in Three Parts:https://jacko.io/async_intro.html
书籍推荐
各位 Rust 爱好者,今天为大家介绍一本《Programming Rust: Fast, Safe Systems Development》(第二版) 是由 Jim Blandy、Jason Orendorff 和 Leonora Tindall 合著的 Rust 编程指南。本书深入探讨了 Rust 语言在系统编程中的应用,着重介绍如何利用 Rust 的独特特性来平衡性能和安全性。书中涵盖了 Rust 的基础数据类型、所有权和借用概念、特征和泛型、并发编程、闭包、迭代器以及异步编程等核心内容。这本更新版基于 Rust 2021 版本,为系统程序员提供了全面而实用的 Rust 编程指导。