• IO模型(多路复用IO)


    IO模型

    I/O(input/output):输入输出

    IO所存在的问题:当我们要输入数据或是要输出数据时,通常需要很长一段时间(对于CPU而言)
    而在等待的过程中,CPU就处于闲置状态,造成了资源的浪费

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

    而IO模型,就是要让我们在等待IO操作的过程中利用CPU,做别的事情

    网络IO经历的步骤和过程

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

    接收数据的过程(如accept,recv):

    1. 等待数据到达网卡,叫做wait data
    2. 从内核copy到应用程序缓冲区,叫做copy data

    发送数据的过程(如send):

    1. 数据从应用程序缓冲区copy到内核

    阻塞IO模型

    默认情况下,写出的TCP程序就是阻塞IO模型

    该模型,提高效率方式:当你执行recv/accept,会进行wait data阶段。

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

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

    非阻塞IO模型

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

    使用方法:将原本阻塞的socket设置为非阻塞

    from socket import *
    import time
    
    server = socket()
    server.bind(('127.0.0.1', 8000))
    server.setblocking(False)	# 将阻塞变成非阻塞
    server.listen(5)
    
    cs = [] # 用来存放已连接的客户端
    msgs = []   # 用来存放客户端发来的数据
    
    while True:
        time.sleep(0.1) # 时间长了,客户端那边会有延迟,时间短了,对CPU的占用又会很大
        try:
            conn, client_addr = server.accept()
            print(client_addr)
            cs.append(conn)
        except BlockingIOError:
            print('没有人')
            # 代码执行到这里说明没有连接需要处理
            # 那就处理收发数据的任务
            for conn in cs[:]:  # 用来接收数据
                try:
                    data = conn.recv(1024)
                    if not data: raise ConnectionResetError
    
                    print(data.decode('utf-8'))
                    msgs.append((conn, data.upper()))
    
                except BlockingIOError:
                    print('没有接受数据')
                except ConnectionResetError:
                    conn.close()
                    cs.remove(conn)
    
            for msg in msgs[:]: # 用来发送数据
                try:
                    msg[0].send(msg[1])
                    msgs.remove(msg)
                except BlockingIOError:
                    pass
                except ConnectionResetError:
                    msg[0].close()
                    msgs.remove(msg)
                    cs.remove(msg[0])
    
    

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

    报错的类型

    1. 服务端接受不到数据(accept,recv)的时候,会报错BlockingIOError
    2. 客户端正常退出,服务端会一直接受空
    3. 客户端断开了,再执行recv会报错ConnectionResetError
    4. 服务器不停地发送数据,就会把缓冲区塞满,然后报错BlockingIOError
    5. 客户端断开了,再执行send会报错ConnectionResetError

    多路复用IO模型(重要)

    属于事件驱动

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

    以点餐为例:
    非阻塞IO,相当于你每次去问,照着菜单挨个问好了没
    多路复用则是你直接和前台说好你有哪些菜,然后直接问前台哪些菜做好了,前台则会给你返回一个列表,里面是已经做好的菜。

    from socket import *
    import select
    
    server = socket()
    server.bind(('127.0.0.1', 8000))
    server.listen(5)
    print('wait...')
    r_list = [server]
    w_list = []
    msgs = {}
    
    while True:
        # 如果所有盘子里都没有东西,就会阻塞在这里
        read_able, write_able, _ = select.select(r_list, w_list, [])
    
        # 处理可读的盘子,也就是接收数据
        for obj in read_able:   # 遍历所有可读数据的socket
            # 有可能是服务器,有可能是客户端
            if obj == server:
                conn, client_addr = server.accept()
                print(client_addr,'连接成功')
                r_list.append(conn)
    
            else:
                try:
                    data = obj.recv(1024)
                    if not data: raise ConnectionResetError
                    print('收到一条新消息', data.decode('utf-8'))
                    # 将要发送数据的socket加入到可写的盘子里让select检测
                    w_list.append(obj)
    
                    # 把要发送的数据以{对象:[数据,数据]}的格式放入容器
                    if msgs.get(obj):
                        msgs[obj].append(data)
                    else:
                        msgs[obj] = [data,]
                except ConnectionResetError:
                    print('连接已断开')
                    obj.close()
                    r_list.remove(obj)
                    msgs.pop(obj) #todo
    
        # 处理可写的盘子,也就是send数据
        for obj in write_able:
            msg_list = msgs.get(obj)
            if msg_list:
                # 遍历发送所有的数据
                for i in msg_list:
                    try:
                        obj.send(i)
                    except ConnectionResetError:
                        msgs.pop(obj)
                        w_list.remove(obj)
                        break
                # 把数据从容器中删除
                msgs.pop(obj)
            # 把这个socket从w_list中删除
            w_list.remove(obj)
    

    对比之前提到的阻塞或是非阻塞IO模型,增加了一个select,来帮我们检测socket的状态,从而避免了我们自己去检测socket带来的开销

    select有四个盘子,是否可读,是否可写,可读,可写,异常的先不管,自己处理异常
    我们只要将任务放入是否可读可写的盘子中,那么一旦任务就绪了,就会返回到可读可写的盘子中,而我们则需要遍历列表,再分别处理读写即可。

    多路复用对比非阻塞

    优势:多路复用可以极大降低CPU的占用率。

    弊端:多路复用本质,多个任务之间是串行的,如果某个任务的耗时较长,将导致其他的任务无法立即执行。

    所以多路复用的最大优点就是高并发,但是能够处理的数据不会太大(如果需要处理的数据量大,也可以一部分一部分的处理)。

    异步IO模型

    异步IO != 非阻塞IO

    因为非阻塞IO在copy data的过程是一个同步的过程,会卡住主线程,只是比较快

    而异步IO,是发起任务后,就可以继续执行其他任务,只有当数据已经拷贝到应用程序缓存区,才会给你的线程发送信号,或者执行回调

    asyncio

    信号驱动IO模型

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

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

  • 相关阅读:
    vs2017默认以管理员运行
    net abp core的appservice中访问httpcontext对象
    .net core 支持apk下载
    EF Core 2.1变化
    .Net 高效开发之不可错过的实用工具
    win10 远程出现身份验证错误 要求的函数不受支持
    分享个百度网盘下载工具
    mysql迁移sqlserver
    2020.08.11 【ABAP随笔】-ITS Mobile 配置
    2020.05.07 【ABAP随笔】- ABAP-SM30删除前检查
  • 原文地址:https://www.cnblogs.com/lucky75/p/11161217.html
Copyright © 2020-2023  润新知