• IO模型总结:bio,nio,多路复用IO


    1、网络编程:

        socket----recv  send方法

             socketserver---多线程下的socket

             接收大数据--方式:先发送长度,接收端接收该长度

        防止粘包---方式1,发送接收交替进行 方式2,最后一次接收可变长度

    2、多线程

             threading---启动多线程方法,join

        守护线程--setDemo(True)主线程结束,启动的守护线程跟着结束

        GIL锁---同一时刻只有一个线程获得CPU计算资源。1、获取时间片2、获得GIL锁  在单核处理器上第2个条件永远满足

        互斥锁--同一时刻只有一个线程修改共享变量

             递归锁--声明同一个锁进嵌套的三个门,这个锁就要用递归锁

        信号量--同一时刻运行n个线程修改共享变量   n=1,2,3,4...

             多线程的协调--event,set标志位,clear标志位,wait标志位,isset方法。  如果标志位isset就前进,否则停下wait标志位

    3、生成者消费者队列

        生产者可以有多个线程,消费者可以多个线程。消费者者不停处理队列中的数据,生产者不停往队列里放数据

    4、多进程

       multiprocessing--父进程ID:os.getppid() 本进程ID:os.getpid() 进程占资源不能无限启动,有进程池:只能同时运行n个进程。

              进程间数据传递:Queue、Pipe 进程间数据共享:Manager

    5、协程

       gevent---是对greenlet的封装,greenlet有switch方法,遇到IO手动切换,gevent是对其的封装,可以自动检测到IO自动切换

            gevent有joinall方法,标记IO操作:monkey.patch_all()

    6、协程的实现

      数据的内核态,用户态。拿recv方法来说,send完成后recv的数据就准备好了,调用recv方法就将数据从内核内存空间拷贝到用户内存空间。

      阻塞IO(没数据接收就阻塞)  非阻塞IO(没数据接收抛异常)  同步IO(阻塞IO,非阻塞IO,IO多路复用/事件驱动型IO 都是同步IO)  异步IO(完全不阻塞理论上的)

           协程的实现方式:IO多路复用。 遇到一个IO就注册一个事件,监测这些事件,执行完成的就返回

           IO多路复用三种实现方式:select、poll、epoll

       epoll和gevent一样, 在Linux底层都是libevent.so实现的

    讲bio,nio之前先看看服务端socket,服务端socket有2个阻塞点,一个是accept的时候,一个是recv的时候

    import socket
    
    sock = socket.socket()
    sock.bind(("127.0.0.1", 8000))
    sock.listen(20)
    
    client, address = sock.accept()    # 阻塞1
    
    ret = client.recv(1024)          # 阻塞2
    
    print(ret)

    bio

    特点:accept和recv都阻塞的情况下,想实现并发,必须要用多线程来实现

    就是每次accept之后,启动一个线程处理与客户端通信,recv,send等

    nio

    随着内核对IO操作的优化,现在accept和recv都不阻塞了,一个线程就能实现IO并发     

    (当然,用多线程并发还能更高,把accept交个线程1,把遍历clients交个线程2, 还可以把clients切两半,交给两个线程)

    伪代码实现:

    import socket, time
    
    
    sock = socket.socket()
    sock.bind(("127.0.0.1", 8000))
    sock.listen(20)
    sock.setblocking(False)    # 1. accept改为非阻塞
    
    clients = []       # 收到的请求都放在这里
    buffer = bytes()   # 模拟:服务端的缓存
    while True:
        time.sleep(1)
        client, address = sock.accept()    # 不阻塞了,无连接client返回null
        if not client:
            print("暂无连接---")
        else:
            client.setblocking(False)   # 2.client改为非阻塞 
            clients.append(client)      # 收到连接加入列表
    
        for client in clients:
            if client.read():   # 客户端读到数据
                buffer.read(client.read())   # 模拟:从客户端里读数据到buffer,position从0移到读到的长度位置
                buffer.plit()            # 模拟:position回到0位置
                ret = buffer.write(10)     # 模拟:从buffer中取出数据进行使用
                print(ret)
            else:
                print("%s : 未收到数据" % client)

    多路复用io

    由于nio会造成资源浪费,所以把socket和client全部交个多路复用器来维护,客户端只需要遍历那些readable或者writable的client就行

    这样节省了资源。

    but,多路复用器只告知状态。read和write操作扔需用户自己完成,这种R/W由用户自己完成的叫同步IO。以上,bio,nio,多路复用io都属于同步IO模型

    伪代码

    import socket, time
    import select
    
    
    sock = socket.socket()
    sock.setblocking(False)
    sock.bind(("127.0.0.1", 8000))
    sock.listen(20)
    
    listens = []
    
    listens.append(sock)
    
    while True:
        read_list, write_list = select.select(listens)     # 多路复用器,用来监测sock和client的状态,分为can_read和can_write
        for item in read_list:
            if item.can_accept():    # 有新连接
                client, address = item.accept()
                client.setblocking(False)
                buffer = bytes()               
                listens.append(client, buffer)
            elif item.can_read():   # 有客户端发数据
                client, buffer = item.client, item.buffer
                buffer.read(client.read())  # 模拟:从客户端里读数据到buffer,position从0移到读到的长度位置
                buffer.plit()  # 模拟:position回到0位置
                ret = buffer.write(10)  # 模拟:从buffer中取出数据进行使用
                print(ret)

    从上面可以引申出:

    阻塞/非阻塞  同步/异步是什么?

    拿现实生活的例子 ,老张=阻塞/非阻塞,   智能水壶=同步/异步      (阻张同水)

    同步阻塞IO:老张立着等普通水壶开了,自己倒杯水  (bio)

    同步非阻塞IO:老张看电视,一会去看一下普通水壶开没开,如果发现开了,自己倒杯水(nio,多路复用io)

    异步阻塞IO:老张立着等智能水壶自己开了给自己送杯开水 (程序中没有这种模型

    异步非阻塞IO:老张边看电视,边等智能水壶开了给自己送杯开水(前提是老张烧开水前要告诉智能水壶,你开了之后自己给我倒杯水过来==>callback)

    伪代码:     智能水壶.烧开水(开水开了,   callback=(return  开水))

    要牢牢的记住:select、poll、epoll都是多路复用器,都是同步IO模型,但epoll是里面效率最高的。

    为什么epoll是效率最高的呢??上面的代码中使用的是select实现的多路复用器,实际上,select和poll都有弊端:

    1. select.select( [.文件描述符1,文件描述符2,..] ) 每次循环调这个方法都传递了重复的参数。

    2. 每次调用select.select( [文件描述符1,文件描述符2,..] ) 的时候内核都需要遍历传递的所有参数

    这样肯定让内核做了重复劳动,有没有一个办法,把用户要监控的sock和client存起来呢?

    因为存起来之后,随网卡有数据包的到来产生中断事件,产生了回调,就知道那个文件描述符有数据到达,内核在忙别的事的时候就把readable的文件描述符准备好了。

                                                                                                   伪代码

    nignx是使用epoll的,怎么看呢?

    `yum -y install strace`     //先安装内核指令监控器,用来监控内核空间和用户空间的交互过程

    `cd /usr/local/nginx/sbin/`      //进入nginx安装目录

    `strace -ff -o out ./nginx`         //开始监控,阻塞

     `ps -ef | grep nginx`

     `ll`   //out开头的是监控记录,out.进程ID

    `vim out.50796`    //50796进程作用是进行socket监听

     `vim out.50797`    //50397是master,作用是管理worker,交互命令看不懂。。。。

     `vim  out.50798`    //工作进程worker,可以看出nginx使用的是epoll模型

    同样redis也是用的epoll模型

    最后一个问题:

    Linux没有实现异步IO(效率并不高),Epoll是一种I/O多路复用技术,用户程序需要主动的去询问内核是否有事件发生,而不是事件发生时内核主动的去调用回调函数,所以不是异步的。

    而Tornado等框架之所以声称是异步的,是框架在epoll的基础上进行了一层封装,由框架去取事件,然后由框架去调用用户的回调函数,所以对于基于框架的用户程序来说,是异步的。

    Tornado是单线程,所以所有的I/O操作必须是非阻塞的

  • 相关阅读:
    241. Different Ways to Add Parentheses java solutions
    89. Gray Code java solutions
    367. Valid Perfect Square java solutions
    46. Permutations java solutions
    116. Populating Next Right Pointers in Each Node java solutions
    153. Find Minimum in Rotated Sorted Array java solutions
    判断两颗树是否相同
    求二叉树叶子节点的个数
    求二叉树第k层的结点个数
    将二叉排序树转换成排序的双向链表
  • 原文地址:https://www.cnblogs.com/staff/p/9919148.html
Copyright © 2020-2023  润新知