IO模型
首先我们来复习一下昨天的协程
协程的目的:单线程下实现并发(切换+保存状态)
yiekd保存状态(自己控制自己)
yield来回切换是一种并发的,但是对效率没有用
切换:遇到I/O时切换,cpu运行时间过长切换
单纯的切没有意义,遇到I/O时会提高效率,才有意义,但是yield做不到
yield是python提供的
多进程多线程由操作系统调度(比起yield来太复杂)
切换要把状态保存下来
控制多个任务来回切换遇到I/O时切,所以就用到了gevent
同步:调用一个功能在原地等着
异步:调用时会立马返回一个结果
阻塞:I/o阻塞,进程和线程不阻塞
非阻塞:不用等了
需了解的五种IO模型:
blocking IO
nonblocking IO
IO multiplexing
signal driven IO
asynchronous IO
当一个read操作发生时,会经历两个阶段:
1.等待数据
2.将数据从内核拷贝到进程中
阻塞IO(blocking IO):
特点:IO执行的两个阶段(等待数据和拷贝数据)都被block了
实际上,除非特别指定,几乎所有的IO接口(包括socket接口)都是阻塞型的
非阻塞IO(non-blocking IO):
在非阻塞式IO中。用户进程其实是需要不断的主动询问kernel数据准备好了没
#服务端 from socket import * import time s=socket(AF_INET,SOCK_STREAM) s.bind(('127.0.0.1',8080)) s.listen(5) s.setblocking(False) #设置socket的接口为非阻塞 conn_l=[] del_l=[] while True: try: conn,addr=s.accept() conn_l.append(conn) except BlockingIOError: print(conn_l) for conn in conn_l: try: data=conn.recv(1024) if not data: del_l.append(conn) continue conn.send(data.upper()) except BlockingIOError: pass except ConnectionResetError: del_l.append(conn) for conn in del_l: conn_l.remove(conn) conn.close() del_l=[] #客户端 from socket import * c=socket(AF_INET,SOCK_STREAM) c.connect(('127.0.0.1',8080)) while True: msg=input('>>: ') if not msg:continue c.send(msg.encode('utf-8')) data=c.recv(1024) print(data.decode('utf-8'))
但是不推荐
我们不能否则其优点:能够在等待任务完成的时间里干其他活了(包括提交其他任务,也就是 “后台” 可以有多个任务在“”同时“”执行)。
但是也难掩其缺点:
#1. 循环调用recv()将大幅度推高CPU占用率;这也是我们在代码中留一句time.sleep(2)的原因,否则在低配主机下极容易出现卡机情况
#2. 任务完成的响应延迟增大了,因为每过一段时间才去轮询一次read操作,而任务可能在两次轮询之间的任意时间完成。这会导致整体数据吞吐量的降低。
多路复用IO(IO multiplexing):
强调:
1. 如果处理的连接数不是很高的话,使用select/epoll的web server不一定比使用multi-threading + blocking IO的web server性能更好,可能延迟还更大。select/epoll的优势并不是对于单个连接能处理得更快,而是在于能处理更多的连接。
2. 在多路复用模型中,对于每一个socket,一般都设置成为non-blocking,但是,如上图所示,整个用户的process其实是一直被block的。只不过process是被select这个函数block,而不是被socket IO给block。
结论: select的优势在于可以处理多个连接,不适用于单个连接
#服务端 from socket import * import select s=socket(AF_INET,SOCK_STREAM) s.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) s.bind(('127.0.0.1',8081)) s.listen(5) s.setblocking(False) #设置socket的接口为非阻塞 read_l=[s,] while True: r_l,w_l,x_l=select.select(read_l,[],[]) print(r_l) for ready_obj in r_l: if ready_obj == s: conn,addr=ready_obj.accept() #此时的ready_obj等于s read_l.append(conn) else: try: data=ready_obj.recv(1024) #此时的ready_obj等于conn if not data: ready_obj.close() read_l.remove(ready_obj) continue ready_obj.send(data.upper()) except ConnectionResetError: ready_obj.close() read_l.remove(ready_obj) #客户端 from socket import * c=socket(AF_INET,SOCK_STREAM) c.connect(('127.0.0.1',8081)) while True: msg=input('>>: ') if not msg:continue c.send(msg.encode('utf-8')) data=c.recv(1024) print(data.decode('utf-8')) 复制代码
异步IO(asynchronousIO)
用户进程发起read操作之后,立刻就可以开始去做其它的事。而另一方面,从kernel的角度,当它受到一个asynchronous read之后,首先它会立刻返回,所以不会对用户进程产生任何block。然后,kernel会等待数据准备完成,然后将数据拷贝到用户内存,当这一切都完成之后,kernel会给用户进程发送一个signal,告诉它read操作完成了。
blocking和non-blocking的区别:
调用blocking IO会一直block住对应的进程直到操作完成,而non-blocking IO在kernel还准备数据的情况下会立刻返回。
non-blocking IO和asynchronous IO的区别:
在non-blocking IO中,虽然进程大部分时间都不会被block,但是它仍然要求进程去主动的check,并且当数据准备完成以后,也需要进程主动的再次调用recvfrom来将数据拷贝到用户内存。而asynchronous IO则完全不同。它就像是用户进程将整个IO操作交给了他人(kernel)完成,然后他人做完后发信号通知。在此期间,用户进程不需要去检查IO操作的状态,也不需要主动的去拷贝数据。