Asyncio总结及示例
相关概念
Future类:
此类 是保存 协程 执行的结果类
重要属性及方法如下:
_result:
存放协程返回的结果
_state:
数据结构为:str
存放协程当前的状态; 如下3种状态:_PENDING(等待执行),_FINISHED(执行结束),_CANCELLED(取消执行)
_callbacks:
数据结构为:list
存放协程在执行结束后 需要执行的回调函数们;
result方法
从 _result中获取结果,并返回
add_done_callback方法
添加协程的回调函数,可以添加多个回调函数;如果 协程当前状态(_state)不是_PENDING,就直接添加到事件循环的_ready中; 否则就添加到 _callbacks中;
set_result方法
标记此协程已经运行完成,并将协程执行结果赋值给 _result;然后将_callbacks中的回调函数们依次放入到事件循环的_ready中;
Task(Future)类:
此类继承自Future类
重要属性如下:
_loop
此属性存的是 事件循环 对象
_coro
此属性存的是 对应的协程对象
__step
此私有方法 通过调用协程的
result = coro.send(None)
促使协程继续向下执行(类似生成器的send方法); 如果返回的result仍是一个Future类的对象,则继续将此__step放到事件循环中,等待下次执行;否则此协程的结果就是result的值;
Handle类:
此类主要作用为 将函数封装成handle,然后放到事件循环的_ready中;
TimerHandle(Handle)类:
此类继承自Handle类,是个定时执行的handle,比如await asyncio.sleep(1)
就是执行的这个handle,然后将此handle放到事件循环的_scheduled中; 等时间到了之后再执行;
BaseEventLoop类(事件循环的类):
这个类 或者说 事件循环 概念是 整个Asyncio异步框架中最重要的部分, 负责handle的存储,定时handle的存储,handle的运行等等
重要属性如下:
_ready:
此数据结构 主要存放 准备好要执行handle对象们;
数据结构 是一个collections.deque 双链表double-ended queue,
至于为什么用 此数据结构,而不 直接使用 list结构,由于此属性的数据操作顺序必须是FIFO,应该是看中此数据结构的 popleft(从左侧弹出一个数据) 时间负责度为O(1),而list为O(n)
_scheduled:
此数据结构 主要存放 定时执行的handle对象们;比如 asyncio.sleep()方法 生成的handle就是放在此数据结构中;等时间到了之后 就会将此数据结构中的handle转移到_ready中;
数据结构是 list,但是其元素排序方式 是按照堆 heapq 的结构排序的;
至于为什么用 堆的方式排序,而不 直接使用 list默认的排序方式,由于此属性 涉及到 弹出最小值(heapq.heappush) 时效率比 min方法更有效率;
_debug:
是否开启debug模式
开启后,如果handle执行时间 超过一定时间(默认0.1s),会打印warning级别的日志 告诉开发人员 可能有些地方 有编写阻塞代码;
call_soon方法
将回调函数 转换为一个handle对象,然后存到_ready中;
run_forever方法
一直while循环运行(主要运行_ready中的handle)直到调用stop为止;代码如下
run_until_complete方法
运行_ready中的handle,直到_ready中没有handle,Future完成为止;其实内部调用了run_forever方法
_run_once方法
每次while循环主要运行的代码部分;也是整个事件循环的主要代码部分; 主要步骤为:
将 底层(系统内核) 已经准备好的socket(各种IO操作(如http请求,mysql数据请求等等)对应的socket,在底层 epoll,select,kqueue等 各种IO多路复用方法给出的结果) 对应的handle放到_ready中;
将_scheduled中的 已经 小于当前时间的定时handle放到_ready中;
对_ready中的handle按照FIFO顺序 依次执行,执行handle过程中,如果遇到新的协程,则会把对应的协程封装成handle放到_ready或者_scheduled中,然后让出cpu, 让后续的handle继续执行,直到_
ready中的所有handle执行完,然后进行下一个while循环;
代码如下
总结
- 何为协程:通过yield关键字 实现2(多)个函数交替执行(一个函数执行到某个地方阻塞了,换另一个函数执行)的方法 就是协程;协程是函数级别的并发;
- 要想实现真正的并发,需要借助Asyncio包,其内置了事件循环等机制,实现了真正意义的协程;
- python的Asyncio包通过生成器(yield/yield from关键字)实现了协程的并发执行;因此在协程催动执行的过程中,需要用到生成器的send,throw等方法;
- Asyncio内置的事件循环可以用第三方的实现;比如 基于libuv用Cython写的uvloop,可以让Asyncio更快;
- epoll和Asyncio的关系:linux的IO多路复用方法如select/epoll等只是事件循环在检测各种底层socket(http请求等)准备好后放到_ready队列中,供事件循环继续处理;
事件循环只是调用了linux底层提供的多路复用方法而已(事件循环将准备好的socket放到_ready中这个操作 相对于linux底层的IO多路复用 来说就是业务层代码), 选择更高效的socket处理方法(如epoll)让Asyncio更快;
import asyncio
import uvloop
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())