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
。
这样,事件循环的实例就被创建出来了。