zoukankan      html  css  js  c++  java
  • 24.IO模型

    IO模型

    ​ IO是指输入输出。当我们要输入数据或是输出数据通常需要很长一段时间,在等待输入的过程中,CPU就处于闲置状态,造成了资源浪费

    ​ 通过IO模型是为了实现在等待IO操作的过程中利用CPU做别的事情

    ​ 注意:IO其实有很多类型,socket网络IO,内存到内存的copy等,相比来说,其中socket网络IO需要等待的时间是是最长的,可以利用这个时间让CPU做其它事情。

    1.网络IO经历的步骤和过程

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

    涉及到的步骤

    1.wait_data

    2.copy_data

    recv accept 需要经历 wait -> copy

    send 只需要经历copy

    2.阻塞IO模型

    默认情况下 之前我们所写的TCP程序就是阻塞IO模型

    当执行recv/accept 会进入wait_data的阶段,

    1.用户进程会主动调用一个block指令,进入阻塞状态,同时让出CPU的执行权,操作系统就会将CPU分配给其它的任务,从而提高了CPU的利用率

    2.当数据到达时,首先会从内核将数据copy到应用程序缓冲区,并且socket将唤醒处于自身的等待队列中的所有进程

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

    3.非阻塞IO模型

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

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

    该模型在没有数据到达时,会跑出异常,我们需要捕获异常,然后继续不断地询问系统内核直到数据到达为止

    可以看出,该模型会大量的占用CPU资源做一些无效的循环, 其效率低于阻塞IO

    4.多路复用IO模型

    属于事件驱动模型

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

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

    select会把已经就绪的放入列表中,我们需要遍历列表,分别处理读写即可

    多路复用对比非阻塞 ,多路复用可以极大降低CPU的占用率

    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("客户端发来一个数据")
               data = obj.recv(1024)
               print("有个客户端说:",data)
               # 将要发送数据的socket加入到列表中让select检测
               w_list.append(obj)
    
       # 处理可写的 也就是send发送数据
       for obj in write_ables:
           obj.send("iam server!".upper())
           # 数据从容器中删除
           # 将这个socket从w_list中删除
           w_list.remove(obj)
    

    注意:多路复用并不完美 因为本质上多个任务之间是串行的,如果某个任务耗时较长将导致其他的任务不能立即执行,多路复用最大的优势就是高并发

    select 的问题:

    1.当进程被唤醒不清楚到底哪个socket有数据,只能遍历一遍

    2.每一次select的执行,都需要将这进程,再加入到等待队列中

    ​ 为了防止重复添加等待队列,当某一次操作完成时,也必须从等待队列中删除进程

    所以select最大限制被设置为了1024 ,如此看来select连多线程都比不上

    于是推出了poll 和 epoll

    poll只是简单对select进行了优化,但是还不够完美 ,epoll才是最后的解决方案

    注意:epoll仅能在linux中使用

    案例:

    import socket
    import select
    
    s = socket.socket()
    s.bind(("127.0.0.1",1689))
    s.listen()
    
    # 创建一个epoll对象
    epoll = select.epoll()
    
    # 注册读就绪事件 (有数据可以读取了)
    # s.fileno()用于获取文件描述符
    epoll.register(s.fileno(),select.EPOLLIN)
    
    
    # 存储文件描述符与socket的对应关系
    fd_sockets = {s.fileno():s}
    
    
    while True:
        # 该函数是阻塞会直到你关注的事件发生
        # 返回值为文件描述符与发生的事件类型  是一个列表 列表中是元组  第一个是描述符 第二个是事件
        for fd,event in epoll.poll():
            print("有socket 搞事情了!")
            sock = fd_sockets[fd] # 取出对应的socket对象
    
            # 判断socket是服务器还是客户端
            if sock == s:
                # 执行对应的接收或发送
                client,addr = sock.accept()
                # 注册客户端的事件
                epoll.register(client.fileno(),select.EPOLLIN)
                # 将对应关系存储到字典中
                fd_sockets[client.fileno()] = client
                print("来了一个客户端....")
                
            elif event == select.EPOLLIN: #客户端的处理
                data = sock.recv(1024)
                if not data:
                    epoll.unregister(fd) # 注销事件
                    fd_sockets.pop(fd) # 从字典中删除
                    sock.close()  # 关闭socket
                    continue
    
                print("%s 发来问候:%s" % (sock,data.decode("utf-8")))
    
                #将事件转换为可写
                epoll.modify(fd,select.EPOLLOUT)
            else:
                sock.send("我是服务器  你丫是谁?".encode("utf-8"))
                # 将事件转换为可读
                epoll.modify(fd, select.EPOLLIN)
    
    

    epoll 如何解决select的两个问题

    1.epoll 把对于等待队列的操作 与阻塞进程分开了

    2.epoll 自己维护了一个等待队列 避免了遍历所有socket

    5.异步IO模型

    异步IO ==

    非阻塞IO不等于异步IO 因为从copy的过程是一个同步任务 会卡主当前线程

    而异步IO 是发起任务后 就可以继续执行其它任务,当数据copy到应用程序缓冲区,才会给你的线程发送信号 或者执行回调

    asyncio 3.4 出现

    6.信号驱动IO模型

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

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

  • 相关阅读:
    sublime text2 中Emmet常用的技巧 和快捷键
    JS 常用函数
    javascript 事件模型 及 event对象属性总结
    Plant Ecology Journal Club 分享主题和文献列表, 1-7, 2018年秋
    Perl FASTA文件拆分合并
    筛选特定ID的条目信息
    计算可塑性指数RDPI
    【文献摘抄】2018年9月
    重金属在超富集植物与作物间作系统中的植物吸收和富集
    【R统计】主成分分析2——主成分回归
  • 原文地址:https://www.cnblogs.com/yellowcloud/p/11166234.html
Copyright © 2011-2022 走看看