协程详解:完全是程序员自己的抽象定义,根本不存在。
首先来看一下我们接触过的概念:
进程:资源单位
线程:执行单位
协程:单线程下实现并发,我们自己在代码层面上检测所有的IO操作,
一旦遇到IO操作就在代码层面完成自己切换达到欺骗CPU,提
升程序运行效率目的。
那么我们需要在代码编写上考虑到如何实现协程,也就是代码的切换和保存状态。
保存状态:通过yield保存我上一次执行的状态,下一次继续从我挂起的地方继续执行
切换状态:如果提到了切换就不得不提IO监测了,我们使用协程在于遇到IO就进行代码层面的切换,那么我们是怎么监测程序IO的呢?
gevent事件(需要自行下载的模块):
调用gevent下的spawn方法来监管程序的IO,模块本身并不能监测常见的IO操作,必须导入规定补丁
from gevent import monkey
monkey.pack_all()
又由于上面操作在使用模块时是必须导入的,所以我们有了简写模式
from gevent import monkey;monkey.pack_all()
通过切换状态我们等到的程序运行时间是耗时最长的那个程序运行所需时间
IO模型简介:
首先我们今天所研究的IO模型都是针对网络IO的
blocking IO 阻塞IO
nonblocking IO 非阻塞IO
IO multiplexing IO 多路复用
signal driven IO 信号驱动IO
asynchronous IO 异步IO
由于信号驱动IO在实际中并不常用,所以主要介绍其余四种IO模型
既然介绍到阻塞那么我们首先来介绍下常见的网络阻塞状态:
accept
recv
recvfrom
我们再来看一下网络数据传输的过程,首先等待数据准备,然后将数据从内核拷贝到进程中去。
虽然send也有IO行为但是我们此时并不考虑。
阻塞IO:
我们之前所接触的都是IO模型,协程除外。
图片分别是应用端和内核,首先第一个阶段是等待对方发数据,在等待过程中应用端阻塞在原地,然后在接受到网络回传数据之后进入下一阶段,内核拷贝数据给应用程序,虽然过程极快但是也需要一定时间的准备,所以整个等待时间为两个阶段。
非阻塞IO模型:
基于阻塞IO模型当应用端接收消息的时候无论是否有数据都会立刻获得一个结果,等待数据阶段不会影响程序运行(同步非阻塞)
如何实现?在套接字中有一个参数setblocking当把它设定为False时会将所有的网络阻塞变为非阻塞。
import socket import time server=socket.socket() server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) server.bind(('127.0.0.1',8083)) server.listen(5) server.setblocking(False) r_list=[] w_list={} while 1: try: conn,addr=server.accept() r_list.append(conn) except BlockingIOError: # 强调强调强调:!!!非阻塞IO的精髓在于完全没有阻塞!!! # time.sleep(0.5) # 打开该行注释纯属为了方便查看效果 print('在做其他的事情') print('rlist: ',len(r_list)) print('wlist: ',len(w_list)) # 遍历读列表,依次取出套接字读取内容 del_rlist=[] for conn in r_list: try: data=conn.recv(1024) if not data: conn.close() del_rlist.append(conn) continue w_list[conn]=data.upper() except BlockingIOError: # 没有收成功,则继续检索下一个套接字的接收 continue except ConnectionResetError: # 当前套接字出异常,则关闭,然后加入删除列表,等待被清除 conn.close() del_rlist.append(conn) # 遍历写列表,依次取出套接字发送内容 del_wlist=[] for conn,data in w_list.items(): try: conn.send(data) del_wlist.append(conn) except BlockingIOError: continue # 清理无用的套接字,无需再监听它们的IO操作 for conn in del_rlist: r_list.remove(conn) for conn in del_wlist: w_list.pop(conn)
IO多路复用模型:
基于非阻塞IO模型我们在其基础上更进一步完善我们的模型内容,操作系统提供的监管机制能够帮助我们监管socket对象和conn对象,并且可以监管多个,只要有人出发了立刻返回一个可执行对象。
当监管的对象只有一个的时候IO多路复用连阻塞IO都比不上,但是IO多路复用可以一次性监管很多对象。
ps:监管机制是操作系统本身就有的并不是我们自己设计的,所以我们在使用监管机制(select)的时候需要我们导入对应的select模块。
那么监管机制是如何帮助我们监管多个对象的呢?
from socket import * import select server = socket(AF_INET, SOCK_STREAM) server.bind(('127.0.0.1',8093)) server.listen(5) server.setblocking(False) print('starting...') rlist=[server,] wlist=[] wdata={} while True: rl,wl,xl=select.select(rlist,wlist,[],0.5) print(wl) for sock in rl: if sock == server: conn,addr=sock.accept() rlist.append(conn) else: try: data=sock.recv(1024) if not data: sock.close() rlist.remove(sock) continue wlist.append(sock) wdata[sock]=data.upper() except Exception: sock.close() rlist.remove(sock) for sock in wl: sock.send(wdata[sock]) wlist.remove(sock) wdata.pop(sock)
监管机制有很多,select机制 poll机制 epoll机制。
但是select在win和linux都有,但是poll只在linux有,虽然两种机制都是能监管多个但是poll能监管的更多。
上述两种监管机制并不是很完美,两种机制都是在操作系统内部遍历全部对象,所以在监管对象特别多的时候可能在效率上存在某些较大的延迟,所以我们有了更加优化的机制epoll机制。
epoll机制 只在linux有,他给每一个监管对象都绑定一个回调机制,一旦有响应,回调机制立刻发起提醒。
针对不同的操作系统还要考虑不同的监管机制就很烦,所以python的大佬们设计了一个模块,根据平台为我们自动选择监管机制selector。
异步IO模型:
异步IO的过程主要是基于异步回调机制,在这里我们在复习一下异步回调机制,回调机制会给每一个进程或者线程绑定一个方法,但运行结束时就会触发立刻返回一个返回值或者执行结果给主进程。
就像是回调机制一样,在异步IO模型中首先线程提交任务,然后直接去做其他的是,然后剩下的等待过程等操作由操作系统完成,当得到返回值结果的时候,操作系统会发送一个信号同时会发送得到的结果给线程,工作结束。
异步IO模型是所有模型中效率最高同时也是使用最广泛地,由于异步IO是需要通过操作系统完成大部分工作的,所以我们没办法直接用程序来进行控制,但是只要我们有需求就会有大佬,所以有大佬为我们封装了整个过程。
asyncio模块
sanic tronado twistod 框架
总结(几种模型横向对比)