Day 36 阻塞与非阻塞 多路复用

匿名 (未验证) 提交于 2019-12-02 23:47:01

Ŀ¼

模型即解决某个问题的固定套路

I/O 指的是输入输出

IO的问题:当我们要输入数据或输出数据通常很长一段时间,当然是对于CPU而言

在等待输入的过程中,CPU就处于闲置状态,没事干,造成了资源浪费

注意:IO其实有很多类型 例如 socket网络IO,内存到内存的copy,等待键盘输入,对比起来socket网络IO需要等待的时间是最长的,这也是咱们重点关注的地方,

学习IO模型要干什么?就是在等待IO操作的过程中利用CPU做别的事情,

操作系统有两种状态:内核态和用户态,当操作系统需要控制硬件时例如接受网卡上的数据,必须先转换到内核态,接受完数据后,要把数据从操作系统缓冲器copy到应用程序的缓冲区,从内核态转为用户态,

涉及到的步骤

buffer 缓冲  cache 缓存  将数据读入到内存所使用的空间叫做**缓冲**  从内存中读取数据存放数据的空间叫作**缓存****  缓冲是为了降低IO次数  缓存是为了提高读取效率
  1. wait_data

  2. copy_data

    recv accept 需要经历 wait->copy

    send 只需要经历copy

默认情况下 TCP程序就是阻塞IO模型

1.执行BLOCK将进程阻塞 2.等待数据 3.copy数据 4.唤醒进程,继续,执行 

该模型提高效率方式,当你执行recv/accept会进入wait_data的阶段,

  1. 你的进程会进入主动调用一个block指令,进程就会阻塞状态,同时让出CPU的执行权,操作系统就会将CPU分配给其他 的任务,从而提高了CPU的利用率
  2. 当数据到达时,首先会从内核拷贝到应程序缓冲区,并且socket将唤醒处于自身的等待队列中的所有进程.

之前使用的多线程 多进程完成的并发其实都是阻塞IO模型,每个线程在执行recv时,也会卡住

非阻塞IO模型与阻塞模型相反,在调用recv\accept时都不会阻塞当前线程,

使用方法:

  1. 将原本阻塞的socket设置为非阻塞

该模型在没有数据到时,会抛出异常,我们需要捕获异常,然后继续不断的询问系统内核知道数据到达为止,可以看出,该模型会大量的占用CPU资源做一些无效的循环,效率低于阻塞型IO

属于事件驱动模型

多个socket使用同一套处理逻辑

如果将非阻塞IO比如是点餐的话,相当于你每次去前台,照着菜单挨个问个遍,

多路复用,是直接问前台那些菜做好了,前台会给你返回一个列表,礼拜就是已经做好的菜

对比阻塞或非阻塞模型增加了一个select来帮我们检测socket的状态,从而避免了我们自己检测socket带来的开销 select会把已经就绪的放入列表中,我们需要遍历列表,分别处理读写即可

案例

import socket import time import select s = socket.socket() s.bind(("127.0.0.1",1688)) # 设置为非阻塞 模型 s.setblocking(True) #在多路复用中  阻塞与非阻塞没有区别 因为select会阻塞直到有数据到达为止 s.listen()  # 待检测是否可读的列表 r_list = [s] # 待检测是否可写的列表 w_list = []  # 待发送的数据 msgs = {}  print("开始检测了") while True:     read_ables, write_ables, _= select.select(r_list,w_list,[])     print("检测出结果了!")     # print(read_ables,"可以收数据了")     # print(write_ables,"可以发数据了")     # 处理可读 也就是接收数据的     for obj in read_ables: # 拿出所有可以读数据的socket         #有可能是服务器 有可能是客户端         if s == obj: # 服务器             print("来了一个客户端 要连接")             client,addr = s.accept()             r_list.append(client)  # 新的客户端也交给select检测了                      else:# 如果是客户端则执行recv 接收数据             print("客户端发来一个数据")             try:                 data = obj.recv(1024)                 if not data:raise ConnectionResetError                 print("有个客户端说:",data)                 # 将要发送数据的socket加入到列表中让select检测                 w_list.append(obj)                 # 将要发送的数据已经socket对象丢到容器中                 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.因为从copy的过程是一个同步任务,会卡住当前线程,而异步IO是发起任务后就可以继续执行其他任务,当数据已经拷贝到应程序缓冲区,才会给你的线程发送信号,或者执行回调

简单的说就是当某个事情发生后会给你的线程发送一个信号,你的线程就可以去处理这个任务

不常用,原始socket的信号太多,处理起来非常繁琐

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!