Day 36 IO模型
IO模型
模型即解决摸个问题的固定套路
IO模型就是指解决IO问题的固定套路
IO就是指输入输出
IO的问题就是当我们要输入数据或者输出数据通常需要很长一段时间(对于CPU而言),在等待输入的过程中,CPU处于闲置状态,早证了资源浪费
注意:IO其实有很多类型,例如socket网络IO,内存到内存的copy,等待键盘输入,对比起来socket网络IO需要等待的时间紧是最长的
网络IO经历的步骤和过程
操作系统有两种状态:内核态和用户态,当操作系统需要控制硬件时,必须先装到内核态,然后把数据从操作系统缓冲区转到内存缓冲区,冲内核态转到用户态
设计到的步骤:
-
wait_data
-
copy_data
recv accpect 需要经历wait->copy
send 只需要经历copy
阻塞IO模型
默认情况下,我们写出的TCP程序就是阻塞IO模型
改模型提高效率方式:当你执行resv/accept时,会进入wait_data阶段
- 进程会主动调用一个block指令,进程进入阻塞状态,同时让出CPU的执行权限,操作系统会将CPU分配给其他的任务,从而提高了CPU的利用率
- 当数据到达时,首先会从内核将数据copy到应用程序缓冲区,并且socket将唤醒处于自生的等待队列中的所有进程
之前使用多线程多进程完成的并发其实都是阻塞IO模型,每个线程在执行recv的时候也会卡住
非阻塞IO模型
非阻塞IO模型与阻塞IO模型相反,在调用recv/sccept是都不会阻塞当前线程
使用方法:将原本阻塞的socket设置为非阻塞
该模型在没有数据到达时,会抛出异常,我们需要捕获异常,然后继续不断的询问系统内核知道数据到达为止,可以看出,该模型会大量的占用CPU资源,做一些无效的循环,效率也可以但是CPU占用太高了,效率低于阻塞IO模型
多路复用IO模型
属于事件驱动模型
多个socket使用同一套处理方案处理逻辑
如果将非阻塞IO比喻是点餐的话,相当于你每次去点餐台按照菜单挨个问个遍;为多路复用就是直接问那些菜做好了(批量问),点餐台会返回给你已给列表,里面就是已经做好的菜,然后挨个吃掉(挨个处理)
在多路服用中,阻塞与非阻塞没有去区别,因为select会阻塞知道有数据到
对比阻塞或非阻塞模型,增加了一个select来帮我们检测socket状态,从而避免了我们自己检测socket带来的开销问题
select会把已经在就绪的放到一个列表中,我们需要遍历列表,分别处理读写即可
import socket,time,select
s=socket.socket()
s.bind(('127.0.0.1',8000))
s.listen(5)
# 待检测是否可读的列表
r_list=[s]
# 待检测是否可写的列表
w_list=[]
# 待发送的数据
msgs={}
print('start check')
while True:
read_ables,write_ables,_=select.select(r_list,w_list,[])
print('finsh check')
# 处理可读列表,也就是接收数据
# 拿出所有可以读数据的socket
for obj in read_ables:
# 有可能是服务器的,也有可能是服务端的
# obj是服务端的
if obj==s:
print('find a client')
client,addr=s.accept()
# 新的客户端也交给select检测
r_list.append(client)
# obj是客户端,并且要执行recv接收数据
else:
print('a msg from client')
try:
data=obj.recv(1024).decode('utf-8')
if not data:raise ConnectionResetError
print(f'client msg:{data}')
# 将要发送数据的socket加入到列表中让select检测
w_list.append(obj)
# 由于容器是一个列表,所以要先判断是否已经存在了列表
if obj in msgs:
msgs[obj].append(data)
else:
msgs[obj]=[data]
except ConnectionResetError:
obj.close()
r_list.remove(obj)
# 处理可写,也就是send发送数据
for obj in write_ables:
msg_list=msgs.get(obj)
if msg_list:
# 遍历发送所有数据
for m in msg_list:
try:
obj.send(m.upper())
except ConnectionResetError:
obj.close()
w_list.remove(obj)
break
# 完成发送把数据从容器中删除
msgs.pop(obj)
# 把socket从w_list中删除
w_list.remove(obj)
多路复用可以极大地降低CPU的占用率
注意:多路复用本质上是多个任务之间串行的,如果某个任务好事较长将导致其他的任务不能立即执行,多路复用醉倒的优势就是高并发
异步IO模型
异步IO不等于非阻塞IO ,因为非阻塞的copy过程是一个同步任务,会卡住当前线程;而异步IO是发起任务后就可以继续执行其他任务,只有当数据已经拷贝到应用程序缓冲区才会给你的线程发一个通知,或者执行回调
信号驱动IO模型
当某个事情发生后,会给你的线程发送一个信号,你的线程就可以取处理这个任务