python的协程和异步io【select|poll|epoll】

协程又叫做微线程,协程是一种用户态的轻量级的线程,操作系统根本就不知道协程的存在,完全由用户来控制,协程拥有自己的的寄存器的上下文和栈,协程调度切换时,将寄存器上下文和栈保存
到其他地方,在切换回来后,恢复之前保存的寄存器的上下文关系,因此协程能保留上一次调用的状态,每次过程重入的时候,就相当于
进入上一次调用的状态

协程一定在单线程中,协程的切换是在线程中切换,和单个线程在cpu之间不停的切换是一样的
但是线程切换是cpu控制的,而协程的切换是用户控制的,操作系统根本无感知;
协程的切换比线程的切换速度要快,效率要高

协程的好处
1、无需线程上下文切换的开销
2、无需原子操作锁定及同步的开销,因为协程是串行的
3、方便切换控制流,简化编程模型
4、高并发,高扩展,低成本,一个cpu支持上万个协程没有问题,所以非常适合高并发处理

协程的缺点
1、无法利用多核的优势,但是协程和进程配合就可以使协程运行在不同的cpu上,就可以利用多核的优势,但是在现实中,大部分场景都没有这个需要
2、只要一个协程阻塞(Blocking),就会阻塞整个协程,因为协程是串行的,这个问题必须要解决,才能让协程大范围应用
解决方法:
1、如果遇到io操作,则进行协程切换,去执行其他的协程,可以用gevent来实现,具体的实现是这样的,比如协程1通过os去读一个file,这个时候就是一个io操作,在调用os的接口
前,就会有一个列表,协议1的这个操作就会被注册到这个列表中,然后就切换到其他协程去处理;等待os拿到要读file后,也会把这个文件句柄放在这个列表中,然后等待在切换到
协程1的时候,协程1就可以直接从列表中拿到数据,这样就可以实现不阻塞了





事件驱动和异步io
事件驱动编程是一种编程范式,这里的程序的执行流由外部事件来决定,他的特点包含一个事件循环,当外部
事件发生时使用回调机制来触发相应的处理,另外两种常见的编程范式是单线程同步以及多线程编程

编程范式
1、事件驱动
2、单线程
3、多线程


异步io就是遇到阻塞就切换,其实就是我们前面学的协程,遇到阻塞立刻切换,达到一个并行的效果,阻塞的工作交给操作系统去干了。
和协程一样,异步io只能在单线程中实现,不能在多线程中实现




主要区别就是一个协程过来后,遇到阻塞,就把阻塞任务丢到操作系统的任务列表中,等再次循环回来后,select,poll,epoll返回给协程的东西是不一样的。
任务列表在内核态,协程在用户态,用户态是协程是不能直接访问内核态的任务列表的,所有需要拷贝整个内核态的任务列表到用户态,供协程去访问和查询


select()
拷贝所有的文件描述符给协程,不论这些任务的是否就绪,都会被返回,那么协程就只能for循环去查找自己的文件描述符,也就是任务列表,select的兼容性非常好,支持linux和windows
但是他有一个支持的最大的任务数,就是1024,但是可以通过修改linux的内核参数可以变大


poll()
3年后,poll诞生了,但是和select一样,几乎没有大的升级,唯一的升级就是没有最大文件描述符的限制


epoll()
到了linux的内核2.6版本才支持epoll,直接由内核之间支持,继承了select和poll的全部优点,被认为是linux下性能最好的多路io就绪通知方法,
epoll和select和poll一样,也只会返回已经就绪的文件描述符或者任务列表,但是当我们调用epoll_wait()获取文件描述符的时候,返回的不是描述符本身,而是一个代表就绪文件描述符的数量的值,也就是
一个索引,你只需要到epoll指定的一个列表或者数组中取得相应的就绪的文件描述符或者就绪的任务列表即可 ,只拷贝就绪的文件描述符,使用内存映射技术(nmap),用户态的进程的可以直接内核空间的内
存,但是仅仅只能访问固定的一块,所有效率会提高,不用在反复拷贝文件描述符


epoll有4个动作:创建,注册,等待,取消注册,很显然我们用不着


epoll和select,poll还有一个本质的区别的就是
select和poll只有在下次在循环回来,再去操作系统获取文件描述符
epoll会直接告诉程序,我们这里已经就绪了,你可以接受数据了,等下一次协程去调用epoll_wait的时候就可以直接拿到就绪的文件描述符
原文地址:https://www.cnblogs.com/bainianminguo/p/7465536.html