python 中的协程
从我个人的理解来说一说 python中的协程,我们知道 linux 中的线程比进程轻量级,切换成本低,协程比线程更轻量级。所以切换成本耕地,是基于生成器来实现的也就是 yield 语句,后来又有 yeild from 子协程的语法出现,生成器是迭代器,迭代器不是生成器,生成器能够输出值,也可以接收值,可以 hang/resume。当然在 python3.5 使用了新的语法 async/await, 本质没啥变化,仅仅是防止在语法上的混淆。可以进行隐式切换或者显式切换,在一个线程中实现多协程切换,asyncio 就是显式的来切换协程。
Asyncio 异步框架
asyncio 框架是建立在 epoll、poll、seledct等功能基之上的,下文统一用 epoll 代替,当然使用那种事件机制取决于操作系统,在使用asyncio时,大部分操作是用asyncio运行任务,运行任务时 asyncio 并没有使用epoll 机制,因为我们知道 epoll 是需要注册文件描述符的,是在使用协程,至于协程和 epoll 怎么结合运行的,下文会细说。epoll 是用来实现异步 web 框架用的。协程使用来运行用户的 task。
Asyncio 的运行流程
简单写一个异步任务,这个任务简单点,因为本篇文章主要讲的是 asyncio 的运行机制而不是 asyncio 的使用
import asyncio
async def print_hello_after3second():
await asyncio.sleep(3)
print("hello")
asyncio.run(print_hello_after3second)
这里使用的 run 这个接口,使用 asyncio 运行异步任务有很多种方式,run 我觉得更像是一个命令行,从外面看接口简单,其实内部帮忙做了很多事情.为了节省篇幅以及使得文章看起来清晰每个代码片段只截取重要部分,其余的省略。
asyncio/runners.py
#run 第一个参数要是个协程
def run(main, *, debug=False):
# loop 理解成 epoll 就好
events.set_event_loop(loop)
#重点在这里
loop.run_until_complete(loop.shutdown_asyncgens())
....
asyncio/base_events.py
def run_until_complete(self, future):
....
# asyncio 会把我们传进来的任务封装成 task,也可以说是 future,task 是 future 的子类
future = tasks.ensure_future(future, loop=self
# 里面有 _run_once 是用来调度事件循环的
self.run_forever()
....
asyncio/task.py
# ensure_future 也是一个传递任务的接口
def ensure_future(coro_or_future, *, loop=None):
....
# 在调用 Task 类中的__init__方法进行初始化,同时将 Task 类中的 _step方法作为回掉函数注册到了事件循环中
task = loop.create_task(coro_or_future)
....
asyncio/base_events.py
#这个方法很重要所以在这里全部列出,里面包含了asyncio的调用思想,调度 task 和 epoll
def _run_once(self):
"""Run one full iteration of the event loop.
This calls all currently ready callbacks, polls for I/O,
schedules the resulting callbacks, and finally schedules
'call_later' callbacks.
"""
print("run once")
sched_count = len(self._scheduled)
if (s