异步编程-Rust

简单入门

概念

异步编程与多线程一样也是一种为了提高程序运行效率设计的一种编程模式,区别在于多线程是利用cpu资源提升效率,而异步是为了避免IO等待时间的资源浪费,比如某个app更新的时候锁定页面我们无法做任何操作,这个时候可以借助异步编程来实现后台下载更新,我们仍然可以使用该app的其他功能

  1. 异步编程的核心原理

非阻塞 I/O:异步操作将 I/O 请求提交后立即返回,线程不等待结果,而是通过回调、事件循环或 async/await 机制在操作完成后恢复执行。
单线程并发:异步编程通常基于单线程事件循环,通过任务调度实现高并发,而非依赖多线程。

  1. 多线程的适用场景与限制

优势:直接利用多核 CPU 并行计算(如视频渲染)。
劣势:线程创建和切换成本高(上下文切换耗时数微秒)。线程间共享资源需同步(如锁、信号量),易引发死锁或竞争条件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
fn get_two_sites() {
// 创建两个新线程执行任务
let thread_one = thread::spawn(|| download("https://course.rs"));
let thread_two = thread::spawn(|| download("https://fancy.rs"));

// 等待两个线程的完成
thread_one.join().expect("thread one panicked");
thread_two.join().expect("thread two panicked");
}

async fn get_two_sites_async() {
// 创建两个不同的`future`,你可以把`future`理解为未来某个时刻会被执行的计划任务
// 当两个`future`被同时执行后,它们将并发的去下载目标页面
let future_one = download_async("https://www.foo.com");
let future_two = download_async("https://www.bar.com");

// 同时运行两个`future`,直至完成
join!(future_one, future_two);
}

async/.await

Rust 内置的语言特性,可以让我们用同步的方式去编写异步的代码。

通过 async 标记的语法块会被转换成实现了Future特征的状态机。 与同步调用阻塞当前线程不同,当Future执行并遇到阻塞时,它会让出当前线程的控制权,这样其它的Future就可以在该线程中运行,这种方式完全不会导致当前线程的阻塞。

1
2
3
4
5
6
7
8
9
10
use futures::executor::block_on;

async fn hello_world() {
println!("hello, world!");
}

fn main() {
let future = hello_world(); // 返回一个Future, 因此不会打印任何输出
block_on(future); // 执行`Future`并等待其运行完成,此时"hello, world!"会被打印输出
}

在一个async fn函数中去调用另一个async fn并等待其完成后再执行后续的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 引入 futures 库的阻塞执行器 block_on,用于在同步上下文中运行异步任务
use futures::executor::block_on;

// 定义异步函数 hello_world,返回实现 Future<Output = ()> 的类型
async fn hello_world() {
// 等待 hello_cat 异步函数执行完成(非阻塞式等待)
// await 会将当前 Future 挂起,将线程控制权交还给执行器
hello_cat().await;
// 前一个 await 完成后继续执行此处
println!("hello, world!");
}

// 定义异步函数 hello_cat,返回实现 Future<Output = ()> 的类型
async fn hello_cat() {
// 立即执行打印,无等待操作
println!("hello, kitty!");
}

fn main() {
// 创建 hello_world 异步任务的 Future 对象
// 此时任务尚未开始执行
let future = hello_world();

// 使用阻塞执行器运行 Future,直到任务完成
// block_on 会阻塞当前线程,驱动 Future 的状态机推进
block_on(future);
}

每个 async 函数在 Rust 中返回一个 Future 对象,该 Future 必须通过执行器(如 block_on)或异步环境中的 await 驱动才能执行

1
2
3
4
5
6
7
8
9
10
11
12
// 使用 block_on(同步环境)
use futures::executor::block_on;
async fn task() { /* ... */ }
fn main() {
block_on(task()); // 阻塞当前线程
}

// 使用 await(异步环境)
#[tokio::main]
async fn main() {
task().await; // 非阻塞挂起,允许其他任务执行
}

Future 特征

异步函数的返回值和被执行的关键,它是一个能产出值的异步计算,需要被执行器poll(轮询)后才能运行

1
2
3
4
5
6
7
8
9
trait SimpleFuture {
type Output;
fn poll(&mut self, wake: fn()) -> Poll<Self::Output>;
}

enum Poll<T> {
Ready(T),
Pending,
}

1、Future 需要被执行器poll(轮询)后才能运行

2、Future 并不能保证在一次 poll 中就被执行完

3、Future可以被完成,会返回 Poll::Ready(result) ,反之则返回 Poll::Pending并且安排一个 wake 函数

4、当未来 Future 准备好进一步执行时, wake 函数会被调用

Pin 和 Unpin