异步运行时
在前面的章节中,我们讲到过异步运行时负责调度执行使用者创建的 Future,那么异步运行时到底是如何工作的呢?在本章中,我们将会实现一个简单的单线程异步运行时,提供异步的网络IO读写操作,以探讨运行时的具体工作机制。
本章节的源代码仓库地址:async-runtime。
在正式开始之前,我们首先明确一下即将实现的运行时的工作原理:

-
用户使用
async fn或者async {}的方式创建Non-Leaf Future,然后使用spawn方法创建一个异步task,并将这个task发送到executor的任务队列中。 -
executor从task_queue中取出task,调用task的poll方法,驱动Non-Leaf Future开始执行(如果已经开始执行了,则从上次的await断点处继续执行),就这样一直执行Future中的代码,直到遇到Leaf Future.await。 -
调用
Leaf Future的poll方法,如果Leaf Future对应的IO事件已经就绪,则直接返回Poll::Ready(data);如果对应的IO事件没有就绪,则调用Reactor的register方法注册等待的IO事件和waker,然后Poll::Pending(Non-Leaf Future将会被挂起),executor可以继续执行其他的task。 -
Reactor会把注册的文件描述符fd、waker保存在BTreeMap<fd, waker>中,然后调用Epoll提供的方法注册在fd上想要等待的event到Epoll系统中。 -
Reactor调用Epoll提供的wait方法获取所有就绪的文件描述符fds,然后遍历fds,通过fd匹配之前在BTreeMap中存储的waker,然后调用waker的wake方法把task发送到executor的执行队列中,这样之前挂起的Non-Leaf Future就能够继续执行了。
通过上面的原理讲解我们可以知道,异步代码之所以高效的原因就是避免了IO对线程的阻塞:
-
当执行一个
task时,如果遇到了没有就绪的 IO 操作,就注册waker到Reactor中,然后挂起这个task,executor就可以继续执行其他的task。 -
当
task等待的 IO 事件就绪时,Reactor就会通过waker唤醒关联的task,然后就可以执行之前挂起的task了。