本文章信息量较大,从IO多路复用,到生成器的使用,再到async、await背后的实现原理,深入浅出,剖析得非常透彻,非常硬核!
这两天因为一点个人原因写了点好久没碰的Python,其中涉及到「协程」编程,上次搞的时候,它还是Web框架tornado特有的feature,现在已经有async、await关键字支持了。思考了一下其实现,回顾了下这些年的演变,觉得还有点意思。
都是单线程,为什么原来低效率的代码用了async、await加一些异步库就变得效率高了?
如果在做基于Python的网络或者Web开发时,对于这个问题曾感到疑惑,这篇文章尝试给一个答案。
0x00开始之前
首先,本文不是带你浏览源代码,然后对照原始代码给你讲Python标准的实现。相反,我们会从实际问题出发,思考解决问题的方案,一步步体会解决方案的演进路径,最重要的,希望能在过程中获得知识系统性提升。
本文仅是提供了一个独立的思考方向,并未遵循历史和现有实际具体的实现细节。
其次,阅读这篇文章需要你对Python比较熟悉,至少了解Python中的生成器generator的概念。
0x01IO多路复用
这是性能的关键。但我们这里只解释概念,其实现细节不是重点,这对我们理解Python的协程已经足够了,如已足够了解,前进到0x02。
首先,你要知道所有的网络服务程序都是一个巨大的死循环,你的业务逻辑都在这个循环的某个时刻被调用:
defhandler(request):#处理请求pass#你的handler运行在while循环中whileTrue:#获取一个新请求request=accept()#根据路由映射获取到用户写的业务逻辑函数handler=get_handler(request)#运行用户的handler,处理请求handler(request)
设想你的Web服务的某个handler,在接收到请求后需要一个API调用才能响应结果。
对于最传统的网络应用,你的API请求发出去后在等待响应,此时程序停止运行,甚至新的请求也得在响应结束后才进得来。如果你依赖的API请求网络丢包严重,响应特别慢呢?那应用的吞吐量将非常低。
很多传统Web服务器使用多线程技术解决这个问题:把handler的运行放到其他线程上,每个线程处理一个请求,本线程阻塞不影响新请求进入。这能一定程度上解决问题,但对于并发比较大的系统,过多线程调度会带来很大的性能开销。
IO多路复用可以做到不使用线程解决问题,它是由操作系统内核提供的功能,可以说专门为这类场景而生。简单来讲,你的程序遇到网络IO时,告诉操作系统帮你盯着,同时操作系统提供给你一个方法,让你可以随时获取到有哪些IO操作已经完成。就像这样:
#操作系统的IO复用示例伪代码#向操作系统IO注册自己