异步运行时
在前面的章节中,我们讲到过异步运行时负责调度执行使用者创建的 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
了。