https://www.cnblogs.com/wangjun187197/p/9642429.html
Python之路--协程/IO多路复用
I/O复用模型
此模型用到select和poll函数,这两个函数也会使进程阻塞,select先阻塞,有活动套接字才返回,但是和阻塞I/O不同的是,这两个函数可以同时阻塞多个I/O操作,而且可以同时对多个读操作,多个写操作的I/O函数进行检测,直到有数据可读或可写(就是监听多个socket)。select被调用后,进程会被阻塞,内核监视所有select负责的socket,当有任何一个socket的数据准备好了,select就会返回套接字可读,我们就可以调用recvfrom处理数据。
正因为阻塞I/O只能阻塞一个I/O操作,而I/O复用模型能够阻塞多个I/O操作,所以才叫做多路复用。
引子:
之前学习过了,线程,进程的概念,知道了在操作系统中进程是资源分配的最小单位,线程是CPU调度的最小单位.按道理来说我们已经算是把CPU的利用率提高很多了.但是我们知道无论是创建多进程还是创建多线程来解决问题,都要消耗一定的时间来创建进程,创建线程,以及管理他们之间的切换.
随着我们对于效率的最求不断提高,基于单线程来实现并发又成为一个新的课题.即只用一个主线程的情况下实现并发.这样就可以节省创建线程进程所消耗的时间.
并发的本质: 切换+保存状态
cpu正在运行一个任务,会在两种情况下切走去执行其他的任务(切换由操作系统强制控制),一种情况是任务发生了阻塞,另外一种情况是该任务计算时间过长
对于单线程下,我们不可避免程序中出现io操作,但如果我们能在自己的程序中(即用户程序级别,而非操作系统级别)控制单线程下的多个任务能在一个任务遇到io阻塞时就切换到另外一个任务去计算,这样就保证了该线程能够最大限度地处于就绪态,即随时都可以被cpu执行的状态,相当于我们在用户程序级别将自己的io操作最大限度地隐藏起来,从而可以迷惑操作系统,让其看到:该线程好像是一直在计算,io比较少,从而更多的将cpu的执行权限分配给我们的线程。
协成的本质就是在单线程下,由用户自己控制一个任务遇到IO阻塞就切换另一个任务去执行,一次来提升效率.
可以控制对个任务之间的切换,切换之前将任务的状态保存下来,以便重新运行时,可以基于暂停的位置继续执行.
作为1的补充;可以检测IO操作,在遇到IO操作的情况下才发生切换
协程介绍:
协程:是单线程下的并发,又称为为线程. 协程是一种用户态的轻量级线程,即协程是由用户程序自己控制调度的.
协程特点:
必须在只有一个单线程里实现并发
修改共享数据不需加锁
用户程序里自己保存多个控制流的上下文栈
一个协程遇到IO操作自动切换到其他协程
Greenlet模块
安装:pip3 install greenlet
greenlet实现状态切换
Gevent模块
安装:pip3 install gevent
Gevent 是一个第三方库,可以轻松通过gevent实现并发同步或异步编程,在gevent中用到的主要模式是Greenlet,它是以C扩展模块形式接入Python的轻量级线程.Greenlet全部运行在主程序操作系统进程的内部,但他们被协作式的调度.
遇到IO主动切换
IO多路复用
IO多路复用作用:检查多个socket是否已经发生变化(是否连接成功/是否已经获取数据)(可读/可写)
当用户进程调用了select,那么整个进程会被block,而同时,kernel会“监视”所有select负责的socket,当任何一个socket中的数据准备好了,select就会返回。
三种模式:
select:最多1024个socket;循环去检测。
poll:不限制监听socket个数;循环去检测(水平触发)。
epoll:不限制监听socket个数;回调方式(边缘触发)。
基于IO多路复用+socket实现并发请求:
IO多路复用
socket 非阻塞(不等待)
异步:执行完某个任务后自动调用我给它的函数
Python中开源 基于事件循环实现的异步非阻塞框架 Twisted
socket发生请求
单线程解决并发
解决并发,多线程
提高并发方案:
--多进程
--多线程
--异步非阻塞模块(Twisted) scrapy框架(单线程完成并发)
单线程完成并发
什么是异步非阻塞?
--非阻塞:不等待
比如创建socket对某个地址进行connect,获取接收数据recv时默认都会等待(连接成功或接收到数据),才执行后续操作.如果设置setblocking(False),以上两个过程就不在等待,到时会报BlockingIOError的错误,只要捕获即可.
--异步,通知,执行完成后自动执行回调函数或自动执行某些操作(通知)
比如做爬虫中向某个地址baidu.com发送请求,当请求执行完成之后自动执行回调函数.
什么是同步阻塞?
--阻塞:等
--同步:按照顺序逐步执行
key_list = ['alex','db','sb']
for item in key_list:
ret = requests.get('https://www.baidu.com/s?wd=%s' %item)
print(ret.text)