什么是IO多路复用
IO多路复用就是通过一种机制,一个进程可以监视多个文件描述符,一旦某个描述符就绪,能够通知程序进行相应的读写操作。
但是select、poll、epoll本质上都是同步IO,都需要在读写事件就绪后自己负责进行读写,这个读写的过程是阻塞的,而异步IO无需自己负责进行读写,异步IO的实现会负责把数据从内核拷贝到内核空间。
epoll
不同于select和poll的忙轮询和无差别轮询,epoll会把每个流发生了怎样的IO事件通知我们。
epoll的相关操作:
- epoll_create()创建一个epoll对象,一般epollfd = epoll_create()
- epoll_ctl ,往epoll对象中增加/删除某个流的某一个事件
- epoll_wait()等待直到注册事件的发生
- 当一个非阻塞流的读写发生缓冲区满或缓冲区非空,write/read会返回-1,并设置error=EAGAIN。而epoll只关心缓冲区非满和缓冲区非空事件
select、poll、epoll之间的区别
select
在内核中,select实现的是用轮询方法,每次检测都会使用FD_SET中的所有句柄,在高并发的情况下select是会被频繁调用的,这样的方法显然没有效率。
时间复杂度O(n)
仅仅知道有IO事件发生,却并不知道是哪几个流,只能轮询所有流,找出能读出数据或者写入数据的流,对他们进行操作。同时处理的流越多,无差别轮询时间越长。
poll
时间复杂度O(n)
本质上和select没有区别,它将用户传入的数组拷贝到内核空间,然后查询每个fd对应的设备状态,但是没有最大连接数的限制,原因是它是基于链表来存储的。
epoll
可以理解为event poll不同于忙轮询和无差别轮询,会把那个流发生怎样的IO事件通知我们。实际上是事件驱动,每个事件关联上fd。
epoll的三大关键要素:mmap、红黑树、链表
本质上都是同步IO,因为它们都需要在读写事件时自己负责进行读写,这个读写过程是阻塞的。
epoll是通过内核与用户空间mmap同一块内存实现的,mmap将用户空间的一块地址和内核空间的一块地址映射到同一块相同的物理地址,使得这块物理内存地址相对于内核和用户都是可见的,减少用户态和内核态之间的数据交换,内核可以直接看到epoll监听的句柄,效率高。
红黑树将存储epoll所监听的套接字,当添加或储存一个套接字的时候都在红黑树上面去处理,红黑树本身插入和删除性能都比较好,时间复杂度O(logN)
通过epoll_ctl函数把添加进来的事件都会被放在红黑树的某个节点内,事件会与相应的设备驱动程序建立回调关系,当相应的事件发生后,就会调用这个回调函数,该回调函数在内核中被称为ep_epoll_callback,这个回调函数把这个事件添加到了rdllist这个双向链表中,一旦有事件发生,epoll就会将该事件添加到双向链表中。当我们调用epoll_wait时,epoll_wait只需要检查双向链表中是否存在注册的事件,效率非常可观,这里需要将发生了的事件复制到用户态内存中即可。
Python中的asyncio
python的time.sleep()与asyncio.sleep()不兼容,会在休眠时间内停止所有运行的内容。
- 使用await和return创建协程函数,调用协程函数必须先用await获取结果
- yield在async def中使用不常见,会创建一个异步生成器。可以使用async for进行迭代,暂时不要使用yield
- 不可以使用yield from
- await只能在async中使用
使用队列queuq.Queue以保证线程安全
- 使用async调度协程
- 默认情况下,异步IO事件循环在单个CPU内核上运行,通常情况下单核CPU运行一个单线程事件循环绰绰有余、
- 事件循环是可以插入的
asyncio的Event Loop维护一个事件列表,一个事件:回调函数的映射关系,一个计时器,计时器:回调的映射关系。轮询事件列表的事件状态,当某事件发生时,执行回调函数,并唤醒协程
当一个计时器事件注册时,会创建一个future对象,为这个future对象创建一个回调函数,当时间到达时,执行这个回调函数,唤醒协程并且将事件的结果返回给事件,需要注意的是内部的协程暂停了,恢复时需要从最外层的协程开始执行。
asyncio这个库做了什么,没做什么
- 控制流的暂停与恢复是通过python内部的generator来实现的
- 协程链,把不同协程链接连接在一起的机制,是通过python内置的支持,即async/await,或者说是生成器的yield from
- Event Loop,这个是asyncio实现的,决定了我们能对什么事件进行异步操作,目前只支持定时器和网络IO操作
- 协程链的控制与恢复,即内部协程暂停了,恢复时需要从最外层的协程开始恢复
异步请求
使用requests与asyncio不兼容,使用aiohttp模块:
- Python的requests模块与异步IO不兼容,requesst是在urllib3之上构建的,后者使用了http和socket模块
- 默认情况下,socket操作处于阻塞状态,像requests.get()不是一个可等待对象,而aiohttp是一个可等待的协程。