博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
IO模型
阅读量:6309 次
发布时间:2019-06-22

本文共 6399 字,大约阅读时间需要 21 分钟。

#1. 同步与异步针对的是函数/任务的调用方式:同步就是当一个进程发起一个函数(任务)调用的时候,一直等到函数(任务)完成,而进程继续处于激活状态。而异步情况下是当一个进程发起一个函数(任务)调用的时候,不会等函数返回,而是继续往下执行当,函数返回的时候通过状态、通知、事件等方式通知进程任务完成。#2. 阻塞与非阻塞针对的是进程或线程:阻塞是当请求不能满足的时候就将进程挂起,而非阻塞则不会阻塞当前进程

 

同步

#所谓同步,就是在发出一个功能调用时,在没有得到结果之前,该调用就不会返回。按照这个定义,其实绝大多数函数都是同步调用。但是一般而言,我们在说同步、异步的时候,特指那些需要其他部件协作或者需要一定时间完成的任务。#举例:#1. multiprocessing.Pool下的apply #发起同步调用后,就在原地等着任务结束,根本不考虑任务是在计算还是在io阻塞,总之就是一股脑地等任务结束#2. concurrent.futures.ProcessPoolExecutor().submit(func,).result()#3. concurrent.futures.ThreadPoolExecutor().submit(func,).result()

 

异步

#异步的概念和同步相对。当一个异步功能调用发出后,调用者不能立刻得到结果。当该异步功能完成后,通过状态、通知或回调来通知调用者。如果异步功能用状态来通知,那么调用者就需要每隔一定时间检查一次,效率就很低(有些初学多线程编程的人,总喜欢用一个循环去检查某个变量的值,这其实是一 种很严重的错误)。如果是使用通知的方式,效率则很高,因为异步功能几乎不需要做额外的操作。至于回调函数,其实和通知没太多区别。#举例:#1. multiprocessing.Pool().apply_async() #发起异步调用后,并不会等待任务结束才返回,相反,会立即获取一个临时结果(并不是最终的结果,可能是封装好的一个对象)。#2. concurrent.futures.ProcessPoolExecutor(3).submit(func,)#3. concurrent.futures.ThreadPoolExecutor(3).submit(func,)

 

阻塞

#阻塞调用是指调用结果返回之前,当前线程会被挂起(如遇到io操作)。函数只有在得到结果之后才会将阻塞的线程激活。有人也许会把阻塞调用和同步调用等同起来,实际上他是不同的。对于同步调用来说,很多时候当前线程还是激活的,只是从逻辑上当前函数没有返回而已。#举例:#1. 同步调用:apply一个累计1亿次的任务,该调用会一直等待,直到任务返回结果为止,但并未阻塞住(即便是被抢走cpu的执行权限,那也是处于就绪态);#2. 阻塞调用:当socket工作在阻塞模式的时候,如果没有数据的情况下调用recv函数,则当前线程就会被挂起,直到有数据为止。

 

非阻塞

#非阻塞和阻塞的概念相对应,指在不能立刻得到结果之前也会立刻返回,同时该函数不会阻塞当前线程。

 

 

 

 

 一、IO发生时涉及的对象和步骤

    对于一个network IO (这里我们以read举例),它会涉及到两个系统对象,一个是调用这个IO的process (or thread),另一个就是系统内核(kernel)。当一个read操作发生时,该操作会经历两个阶段:

#1)等待数据准备 (Waiting for the data to be ready)#2)将数据从内核拷贝到进程中(Copying the data from the kernel to the process)

 

五种IO Model:

   阻塞IO          (blocking IO)

   非阻塞IO      (nonblocking IO)

   IO多路复用        ( IO multiplexing)

   异步IO       (signal driven IO)

   信号驱动(了解)   (asynchronous IO)

    
   

 

阻塞IO

socket模块默认就是阻塞的问题:同一时间只能服务一个客户端方法1:多线程    优点:如果并发量不高 效率是较高的 因为每一个客户端都有单独线程来处理    弊端:不可能无限的开启线程 线程也需要占用资源方式2:多进程    优点: 可以多个CPU并行处理    弊端: 占用资源非常大,一旦客户端稍微多一点 立马就变慢了线程池:    优点: 保证了服务器正常稳定运行,还帮你负责创建和销毁线程,以及任务分配    弊端: 一旦并发量超出最大线程数量,就只能等前面的运行完毕进程池:真正导致效率低的是阻塞问题  但是上述几个方法 并没有真正解决阻塞问题 仅仅是避开了阻塞问题

 

非阻塞IO

  setblocking(False)

  在非阻塞式IO中,用户进程其实是需要不断的主动询问kernel数据准备好了没有

import socketimport times = socket.socket()s.bind(("127.0.0.1",9999))s.listen()# 设置socket 是否阻塞 默认为Trues.setblocking(False)# 所有的客户端socketcs = []# 所有需要返回数据的客户端send_cs = []while True:    # time.sleep(0.2)    try:        c,addr = s.accept() # 三次握手        print("run accept")        cs.append(c) #存储已经连接成功的客户端    except BlockingIOError:        # 没有数据准备 可以作别的事情        # print("收数据")        for c in cs[:]:            try:                data = c.recv(1024)                if not data:                    c.close()                    cs.remove(c)                print(data.decode("utf-8"))                # 把数据和连接放进去                send_cs.append((c, data))                #c.send(data.upper()) # io                # send也是io操作  在一些极端情况下 例如系统缓存满了 放不进去 那肯定抛出                # 非阻塞异常  这时候必须把发送数据 单独拿出来处理 因为recv和send都有可能抛出相同异常                # 就无法判断如何处理            except BlockingIOError:                continue            except ConnectionResetError:                c.close()                # 从所有客户端列表中删除这个连接                cs.remove(c)        # print("发数据")        for item in send_cs[:]:            c,data = item            try:                c.send(data.upper())                # 如果发送成功就把数据从列表中删除                send_cs.remove(item)            except BlockingIOError: # 如果缓冲区慢了 那就下次再发                continue            except ConnectionResetError:                c.close() # 关闭连接                send_cs.remove(item) # 删除数据                # 从所有客户端中删除这个已经断开的连接                cs.remove(c)
非阻塞IO、套接字服务端

 补充:

li = [1,2,3,4,5,6]for i in li[:]:                     #    li.remove(i)print(li)
迭代取值不修改元素

 

 但是非阻塞IO模型绝不被推荐

其优点:#能够在等待任务完成的时间里干其他活了(包括提交其他任务,也就是 “后台” 可以有多个任务在“”同时“”执行)其缺点#1. 无限循环调用recv(),CPU占用率太高;#2. 任务完成的响应延迟增大了,因为每过一段时间才去轮询一次read操作,而任务可能在两次轮询之间的任意时间完成。这会导致整体数据吞吐量的降低。

 

 

多路复用IO

IO多路复用    用一个线程来并发处理所有的客户端    原本我们是直接向操作系统 要数据,        如果是阻塞IO  没有数据就进入阻塞状态        非阻塞IO   没有数据就抛出异常 然后继续询问操作系统    在多路复用模型中,筛选出已经准备就绪的socket,会将可读和科协的分别放到不同的列表中    既然是已经就绪 那么执行recv或是send 就不会在阻塞    select模块只有一个函数就是select    参数1:r_list 需要被select检测是否是可读的客户端  把所有socket放到该列表中,select会负责从中找出可以读取数据的socket    参数2:w_lirt 需要被select检测是否是可写的客户端  把所有socket放到该列表中,select会负责从中找出可以写入数据的socket    参数3:x_list 存储要检测异常条件 ....忽略即可    返回一个元组 包含三个列表        readables 已经处于可读状态的socket  即数据已经到达缓冲区        writeables 已经处于可写状态的socket  即缓冲区没满 可以发送...        x_list:忽略    从可读或写列表中拿出所有的socket 依次处理它们即可
import socketimport timeimport selects = socket.socket()s.bind(("127.0.0.1",9999))s.listen()# 在多路复用中 一旦select交给你一个socket 一定意味着 该socket已经准备就绪 可读或是可写# s.setblocking(False)r_list = [s]w_list = []# 存储需要发送的数据 已及对应的socket  把socket作为key 数据作为valuedata_dic = {}while True:    readables,writeables,_ = select.select(r_list,w_list,[])    # 接收数据 以及服务器建立连接    for i in readables:        if i == s:# 如果是服务器  就执行accept            c,_ = i.accept()            r_list.append(c)        else: # 是一个客户端端 那就recv收数据            try:                data = i.recv(1024)                if not data: #linux 对方强行下线或是 windows正常下线                    i.close()                    r_list.remove(i)                    continue                print(data)                # 发送数据 不清楚 目前是不是可以发 所以交给select来检测                w_list.append(i)                data_dic[i] = data # 把要发送的数据先存在 等select告诉你这个连接可以发送时再发送            except ConnectionResetError:# windows强行下线                i.close()                r_list.remove(i) # 从检测列表中删除    # 发送数据    for i in writeables:        try:            i.send(data_dic[i].upper()) # 返回数据            #data_dic.pop(i)            #w_list.remove(i)        except ConnectionResetError:            i.close()        finally:            data_dic.pop(i) # 删除已经发送成功的数            w_list.remove(i) # 从检测列表中删除这个连接  如果不删除 将一直处于可写状态
IO多路复用,套接字服务端

 

 select监听fd变化的过程分析:

#用户进程创建socket对象,拷贝监听的fd到内核空间,每一个fd会对应一张系统文件表,内核空间的fd响应到数据后,就会发送信号给用户进程数据已到;#用户进程再发送系统调用,比如(accept)将内核空间的数据copy到用户空间,同时作为接受数据端内核空间的数据清除,这样重新监听时fd再有新的数据又可以响应到了(发送端因为基于TCP协议所以需要收到应答后才会清除)。

 

该模型的优点:

#相比其他模型,使用select() 的事件驱动模型只用单线程(进程)执行,占用资源少,不消耗太多 CPU,同时能够为多客户端提供服务。如果试图建立一个简单的事件驱动的服务器程序,这个模型有一定的参考价值。

 

该模型的缺点:

#首先select()接口并不是实现“事件驱动”的最好选择。因为当需要探测的句柄值较大时,select()接口本身需要消耗大量时间去轮询各个句柄。很多操作系统提供了更为高效的接口,如linux提供了epoll,BSD提供了kqueue,Solaris提供了/dev/poll,…。如果需要实现更高效的服务器程序,类似epoll这样的接口更被推荐。遗憾的是不同的操作系统特供的epoll接口有很大差异,所以使用类似于epoll的接口实现具有较好跨平台能力的服务器会比较困难。#其次,该模型将事件探测和事件响应夹杂在一起,一旦事件响应的执行体庞大,则对整个模型是灾难性的。

 

转载于:https://www.cnblogs.com/pdun/p/10516467.html

你可能感兴趣的文章
PHP 分割字符串
查看>>
java 基于QRCode、zxing 的二维码生成与解析
查看>>
关于职业规划的一些思考
查看>>
img垂直水平居中与div
查看>>
Fabrik – 在浏览器中协作构建,可视化,设计神经网络
查看>>
防恶意注册的思考
查看>>
http2-head compression
查看>>
C# 命名空间
查看>>
订餐系统之同步美团商家订单
查看>>
使用ArrayList时设置初始容量的重要性
查看>>
Java Web-----JSP与Servlet(一)
查看>>
Maven搭建SpringMVC+Mybatis项目详解
查看>>
关于量子理论:最初无意的简化,和一些人有意的强化和放大
查看>>
CentOS 6.9通过RPM安装EPEL源(http://dl.fedoraproject.org)
查看>>
“区块链”并没有什么特别之处
查看>>
没有功能需求设计文档?对不起,拒绝开发!
查看>>
4星|《先发影响力》:影响与反影响相关的有趣的心理学研究综述
查看>>
IE8调用window.open导出EXCEL文件题目
查看>>
python之 列表常用方法
查看>>
vue-cli脚手架的搭建
查看>>