python3中增加的重要特性之一即为asyncio,其提供了异步编程的原语支持,从而能够让python在事件驱动、协程协同等方面的编程场景大杀四方。
事件循环EventLoop是异步编程中的核心概念之一。python的异步IO,就从事件循环的实现开始讲起。
首先看一段示例代码:
1 | async def _test_run_main(): |
通过async def定义的函数,其返回值是一个异步协程coroutine。协程相当于是事件循环里的一个单位任务,通过asyncio.run接口就可以将其运行起来。因此我们先来看asyncio.run的实现:
1 | def run(main, *, debug=None): |
在asyncio.run中,首先会检查协程合法性以及当前线程是否有已在跑的事件循环,之后会新启动一个事件循环,并设置为当前线程在跑的事件循环,最后等待协程完成。完成后,会关闭事件循环,并取消当前线程事件循环的设置。
事件循环的诞生,便是从new_event_loop方法开始了。以windows为例,我们来看下当创建一个新的事件循环时,会发生哪些调用:
1 | # runners.py |
事件循环创建的策略有多种,在调用new_event_loop时,实质是执行默认事件循环策略的创建方法。以windows为例,默认策略是ProactorEventLoop。
proactor模型本身为异步IO而生,其基本工作原理如下:
- 用户态应用预先设定一组针对不同IO操作完成事件的回调(
Handler),同时向内核注册一个完成事件的dispatcher(也就是proactor) - 用户态线程发起异步IO操作后会即刻返回结果
- IO操作在内核执行完成后会通知
proactor,proactor根据完成事件的类型,触发对应的完成事件回调
在windows下ProactorEventLoop实际是使用了IOCP模型,中文翻译叫IO完成端口,其基本工作原理如下:
- 通过
CreateIoCompletionPort创建完成端口- 完成端口,实质是一个用于缓存IO完成事件的队列
- 创建一组
worker thread关联完成端口 - 创建
listen server listen server在accept到客户端连接后,创建PerHandleData实例,将客户端socket与PerHandleData实例与完成端口关联起来。执行上述的关联后,可以通过WSARecv发起接收客户端数据的异步IO操作,然后继续accept- 在
worker thread中,通过GetQueueCompletionStatus方法获取IO操作的完成结果。如WSARecv完成后,可以直接提取接收到的客户端数据,执行对应的操作 listen server退出,通过PostQueuedCompletionStatus向完成端口发送特殊的数据包,用以让worker thread退出
了解了proactor和iocp的基本工作原理后,我们就可以看python版ProactorEventLoop的具体实现了。
1 | # windows_events.py |
当ProactorEventLoop实例初始化时,会先创建IocpProactor实例,里面通过CreateIoCompletionPort创建了一个完成端口。之后再调用BaseProactorEventLoop的初始化函数。BaseProactorEventLoop先初始化BaseEventLoop,然后设置proactor,并创建了一组socketpair。
这样,事件循环的实例就被创建出来了。