Rust 异步运行时(Async Runtime)是一个在 Rust 程序主逻辑(main 函数)开始执行之前和之后,负责为其提供关键执行环境的代码库和一组服务。
它的核心任务是管理异步任务(如 async/.await)所需的复杂调度、上下文切换和事件驱动I/O。
一、什么是异步运行时?
它不是一个单一的东西,而是一个由多个组件构成的生态系统,专门用于驱动异步代码。
1、核心组件包括:
- 【组件1】执行器(Executor)
职责:
就像一个公司的“项目经理”。它 持有/管理 一个或多个(线程池),并从任务队列中取出准备好异步任务(Future),放到线程上执行。一个Future在无法立即取得进展时(比如等待网络数据),会被挂起,执行器就会去执行下一个任务。
工作原理:
(1) 你通过 #[tokio::main] 等方式将 main 函数交给运行时,或者手动将 Future 塞给执行器(例如 tokio::spawn(...))。
(2) 执行器在其线程上不停地轮询(poll) 它管理的所有 Future。—— 备注:轮询任务队列中的Future
(3) 当一个 Future 被轮询并返回 Poll::Pending 时,执行器就知道这个 Future 在等待外部事件(如网络数据包、定时器到期),于是它先把这个 Future 挂起,转而去轮询其他可以继续执行的 Future。
(4) 当反应器通知执行器“某个事件准备好了”(比如网络数据到了),执行器会找到正在等待这个事件的 Future,并再次轮询它,这次它很可能就会返回 Poll::Ready 。
- 【组件2】反应器/事件循环(Reactor/Event Loop)
职责:
就像公司的“前台”或“调度中心”。它利用操作系统提供的机制,与操作系统的异步接口交互(如:Linux的epoll,MacOS的kquue,Windows的IOCP)。监听和收集各种I/O事件(例如“这个Socket有数据可读了”、“那个文件写入完成了”、“那个定时器到期了”等)。当事件发生时,它会通知对应的Waker。
工作原理:
(1) 它向操作系统注册需要监听的事件和其对应的唤醒机制(Waker)。
(2) 它阻塞地等待操作系统发出事件通知。
(3) 当事件发生时,它通知执行器:“嘿,你之前关心的那个事件发生了,可以去重新轮询等待它的那个 Future 了!”
- 【组件3】Waker(连接执行器和反应器的桥梁)
每个异步任务都有一个关联的 Waker。当一个任务因为等待 I/O 而阻塞时,它会将自己的 Waker 注册到反应器上。当反应器检测到该任务等待的事件就绪时(例如数据到达),就会调用 Waker 的 wake() 方法。这个方法的作用就是告诉执行器:“嘿,这个任务之前等的东西准备好了,可以重新把它放进队列里执行了!”
备注:这是异步运行时中一个非常精巧的设计。当一个 Future 返回 Poll::Pending 时,它会附上一个 Waker 对象。反应器在事件发生时,会调用与这个事件关联的 Waker 的 wake() 方法。这个方法会通知执行器:“喂,那个之前挂起的 Future 现在可能有进展了,快再来轮询它一下!(所谓的轮询,就是再次查看一下任务的准备状态是否为
Poll::Ready
)” 这样就避免了执行器盲目地、不停地轮询所有 Future,极大地提高了效率。
2、常见的选择
Rust 标准库只提供了定义异步操作的接口(如 Future
trait),但没有提供具体的运行时实现。这意味着你需要选择一个第三方库来充当运行时:
- Tokio:最流行、功能最全面的运行时,提供了高性能的网络、文件系统、定时器等。
- async-std:旨在提供与标准库类似 API 风格的异步运行时。
- smol: 一个非常小巧且高效的运行时。
当你写 #[tokio::main]
时,你就是在明确选择并使用 Tokio 运行时。 这个宏会将你的普通 main
函数转换为一个由 Tokio 运行时驱动的异步入口点。
二、异步编程的基础:async/.await 和 Future
首先,你需要明白 Rust 语言本身只提供了最基础的异步编程工具,而不是一个完整的运行时:
- async: 将一个代码块标记为异步的。当你写
async fn foo() {...}
时,这个函数并不会直接执行,而是返回一个实现了Future
trait 的对象。 - Future: 这是一个核心 trait,它代表一个可能尚未完成的异步计算。它有一个核心方法:
poll
(轮询)。poll
方法会检查这个计算是否完成:- 如果完成了,就返回
Poll::Ready(结果)
。 - 如果没完成,就返回
Poll::Pending
,并且安排好在未来某个时间点(当它可能准备好时),需要再次被“问一下”(即再次调用poll
)。
- 如果完成了,就返回
- .await: 在
async
函数内部,你可以在一个Future
上使用.await
。这相当于厨师看到点菜单上写着“等水烧开”,他就把这张单子先放下,先去处理别的单子。.await
就是一个让出控制权的点。
关键点:Rust 标准库只定义了 Future
这个接口和 async
/.await
语法,但并没有提供驱动(轮询)这些 Future
的机制。这个驱动机制,就是异步运行时的职责。