tornado 的协程调度原理

date
Mar 14, 2021
slug
python-tornado-coroutine
status
Published
tags
Python
summary
讨论 tornado 中协程是怎样执行的
type
Post
本文讨论 tornado 的协程实现原理,简单做了一份笔记。
首先看一段最常见的 tornado web 代码:
其中最后一行代码 tornado.ioloop.IOLoop.current().start() 启动服务。带着几个问题往下看:
  • 知道 yield 可以暂存执行状态,等「合适的时机」重新恢复执行,那么保存的状态到哪去了?
  • 上一个问题中「合适的时机」是到底是什么时候?
  • 继续接上一个问题,具体是怎么恢复执行的?
 
IOLoop 类相当于是对多路复用的封装,起到事件循环的作用,调度整个协程执行过程。
查看 IOLoop 的源码,可以看到 IOLoop 继承自 Configurable,PollIOLoop 又继承自 IOLoop。当 IOLoop 启动时,会确定使用哪一种多路复用方式,epoll、kqueue 还是 select?
 
PollIOLoop 中 initalize 方法中调用 add_handler 方法,注册对应事件的处理函数,如 socket 可读时,回调哪个函数去处理。
 

IOLoop 和协程之间的信使:Future

Future 对象起到“占位符”的作用,协程的执行结果会通过 set_result 方式写入其中,并调用通过 add_done_callback 设置的回调。
 

恢复唤醒协程的 Runner

协程每生成一个 Future,都会生成对应的一个 Runner,并将 Future 初始化注入都其中。Runner 的 run 方法中,通过 self.gen.send(Future) 来启动 Future,当 Future 完成时,将其设置成 done,并回调其预设的 callback。
 

回答第一个问题:协程的状态保存到哪去了:

IOLoop 中通过 add_future 调用实现类 PollIOLoop 中的 add_callback 方法,其中通过 functools 生成偏函数,放入 _callbacks 列表,等待被回调执行。
 

第二个问题:「合适的时机」是什么?

IOLoop 实际上就是对多路复用的封装,当底层 epoll_wait 事件发生时,即会通知 IOLoop 主线程。
这一段是 IOLoop 中等待多路复用的事件,以及处理事件。
 

第三个问题:具体是怎么恢复的。

Runner 通过不断 check Future 的状态,最后调用 callback 来返回结果。
 

总结

首先 tornado 对多路复用系统调用做了封装,来实现非阻塞 web 服务。
其次 tornado 通过 yield+Future+Runner 实现了生成 Future,Runner 监控结果,回调 callback 来实现协程的执行。
 
参考:

© 菜皮 2020 - 2024