Rust

2025年11月03日更新 13 人订阅
原价: ¥ 6 限时优惠
专栏简介

Rust Async/Await 实战:从串行到并发,掌握 block_on 与 join! 的异步魔力

RustAsync/Await实战:从串行到并发,掌握block_on与join!的异步魔力Rust以其零成本抽象和内存安全特性在系统编程领域备受推崇。在构建高性能网络服务或处理高并发任务时,理解和运用其异步编程模型至关重要。与Go、Node.js等语言提供“开箱即用”的异步方案

Rust Async/Await 实战:从串行到并发,掌握 block_onjoin! 的异步魔力

Rust 以其零成本抽象和内存安全特性在系统编程领域备受推崇。在构建高性能网络服务或处理高并发任务时,理解和运用其异步编程模型至关重要。与 Go、Node.js 等语言提供“开箱即用”的异步方案不同,Rust 采取了更通用的方式,提供了 Futureasync/await 这样的基础模块。本文将深入浅出地对比系统线程与异步模型的差异,并通过一系列实战代码示例,重点讲解如何使用 futures::executor::block_onfutures::join! 等工具,从最基础的串行调用逐步迈向高效的并发执行,解锁 Rust 并发模型的强大威力。

Rust 异步编程 Async Rust

系统线程

由操作系统管理,抢占式多任务(preemptively multi-tasked)

  • OS Scheduler (调度器)可随时中断一个线程
  • 调度器本身是相对”重量级“的。需要保存当前线程的状态,加载下一个线程的状态,然后恢复执行
  • 对任务的调度只有有限的控制权

异步模型

Async Model,协作式多任务(cooperatively multi-tasked)

  • 可以只运行在一个线程上,也可以把任务分布到多个线程上
  • 一个异步任务只有在主动让出控制权(yield control)时才会被中断,执行器进程本身仍然可能被操作系统调度器中断。
  • 异步任务非常轻量,它只包含执行栈(局部变量和函数调用)以及用于恢复任务执行的必要信息(例如:当一个网络操作完成后,如何恢复)

何时使用 Async,何时使用系统线程?

场景 使用系统线程 使用 Async
任务运行时间 长时间运行的任务 短时间运行的任务
任务类型 CPU 密集型任务 I/O 密集型任务
并行性需求 需要真正并行运行的任务 需要并发运行的任务
延迟需求 需要最小且可预测的延迟 能利用等待时间来做其他事,提升吞吐量的任务

不适合的场景示例

  • 不适合 async 的场景:任务会占用大量 CPU,且在逻辑上很长时间都不会让出控制权时,否则会让整个系统的异步性能下降。
  • 不适合系统线程的场景:当你为每个网络客户端都创建一个系统线程,而这些线程大多数时间都在等待 I/O 的场景,这会导致耗尽内存或线程资源。

最好的做法是:将两者结合使用,这样才能真正发挥 Rust 并发模型的威力。

Rust 与 Async/Await

  • NodeJs、Go、C#、Python 等语言都实现了一套有明确设计主张的且开箱即用(batteries included)的 Async/Await 方案
  • C++ 和 Rust 都采用了一种更为通用(agnostic)的方式,提供了构建的基础模块,将组装成框架的工作留给了开发者

Async Rust 实操

示例一

use futures::executor::block_on;

async fn hi() {
    println!("Hello, world!");
}

// 1. async fn 可以执行 non-async fn
// 2. non-async fn 不可以执行 async fn,除非有 executor

fn main() {
    let func = hi(); // executor
    block_on(func);
}

这段 Rust 代码展示了 异步函数 (async fn) 的基本使用和如何在 非异步函数 (non-async fn) 中执行它。hi 函数被声明为 async fn,这意味着它返回一个 Future。Future 表示一个可能还没有完成的异步操作。非异步函数,例如 main 函数,不能直接调用并等待一个 async fn 完成,因为它需要一个 执行器 (executor) 来驱动 Future 的执行。代码中使用的 use futures::executor::block_on; 引入了 block_on 这个执行器。在 main 函数中,let func = hi(); 实际上只是创建了一个 Future(即那个异步操作的定义),并没有开始执行。然后,block_on(func); 充当了阻塞式的执行器,它会阻塞当前的线程(即 main 函数)直到 hi() 返回的 Future 完成,从而输出 "Hello, world!"。简而言之,这段代码演示了如何使用 block_on 库中的工具在同步的 main 函数中同步地运行一个异步函数。

运行

➜ cargo run
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.01s
     Running `target/debug/async_rust`
Hello, world!

这段运行结果表明 Rust 程序已成功编译和执行。具体来说,cargo run 命令首先完成了代码的编译(Finished 'dev' profile...),这个过程非常快,只用了 $0.01$ 秒。然后,它执行了生成的调试版本程序(Running 'target/debug/async_rust'),程序的执行结果输出了 "Hello, world!"。这个输出是程序中 async fn hi() 内部的 println! 宏在 main 函数通过 block_on(func) 被成功驱动执行后产生的。这证明了异步函数被执行器正确地运行。

示例二

use futures::executor::block_on;

async fn hi() {
    println!("Hello, world!");
    hello_rust().await;
}

// 1. async fn 可以执行 non-async fn
// 2. non-async fn 不可以执行 async fn,除非有 executor

async fn hello_rust() {
    println!("Hello, Rust!");
}

fn main() {
    let func = hi(); // executor
    block_on(func);
}

这段 Rust 代码进一步展示了异步函数 (async fn) 之间的串联调用hi 函数和 hello_rust 函数都被定义为 async fn。在 hi 函数内部,它首先打印 "Hello, world!",然后使用 .await 关键字来暂停自身的执行,直到另一个异步函数 hello_rust() 完成。hello_rust() 只打印 "Hello, Rust!"。由于 hi() 内部有 .await,它会等待 hello_rust() 运行完毕才会继续。最后,在同步的 main 函数中,block_on(func) 充当执行器,阻塞地驱动整个异步操作 (hi() 及其内部调用的 hello_rust()) 运行直到完成,因此程序会按顺序输出两条信息。这段代码的核心在于说明 async fn 之间可以通过 .await 实现非阻塞的等待和协作,而 block_on 则是将整个异步...

剩余50%的内容订阅专栏后可查看

点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论