zoukankan      html  css  js  c++  java
  • I/O模型的使用

    阻塞I/O
    1.定义:在执行IO操作时如果执行条件不满足则阻塞。阻塞IO是IO的默认形态。
     
      * 有那些I/O操作: 文件读写, 网络传输
      * 阻塞: 进程由执行态进入阻塞态

    2.效率:阻塞IO是效率很低的一种IO。但是由于逻辑简单所以是默认IO行为。

    3.阻塞情况:
     
      * 因为某种执行条件没有满足造成的函数阻塞
        e.g.  input -- accept -- recv

      * 处理IO的时间较长产生的阻塞状态
        e.g. 网络传输,大文件读写
    4. 代码
     
    a = input()
    print(a)
    input
    from socket import *
    s = socket()
    s.bind(('0.0.0.0', 8888))
    s.listen(5)
    print('Listen the port', 8888)
    c, addr = s.accept()
    print("Connect from", addr)
    accept
    from socket import *
    s = socket()
    s.bind(('0.0.0.0', 8888))
    s.listen(5)
    print('Listen the port', 8888)
    c, addr = s.accept()
    print("Connect from", addr)
    
    data = c.recv(1024)
    print(data)
    recv / recvfrom

     

    非阻塞I/O

    1. 定义 :通过修改IO属性行为,使原本阻塞的IO变为非阻塞的状态。

      * 设置套接字为非阻塞IO
        >sockfd.setblocking(False)  -->  设置为非阻塞I/O后, 若产生阻塞, 则会报错: BlockingIOError
        功能:设置套接字为非阻塞IO
        参数:默认为True,表示套接字IO阻塞;设置为False则套接字IO变为非阻塞
     
      * 超时检测 :设置一个最长阻塞时间,超过该时间后则不再阻塞等待。
          >sockfd.settimeout(sec)  --> 超时会引发_socket模块中的timeout异常
          功能:设置套接字的超时时间
          参数:设置的时间
     
    """
    block_io.py
    socket 非阻塞IO示例
    """
    
    from socket import *
    from time import *
    
    # 日志文件
    f = open('log.txt','a+')
    
    # tcp 服务端
    sockfd = socket()
    sockfd.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
    sockfd.bind(('0.0.0.0',8888))
    sockfd.listen(5)
    
    # 非阻塞设置
    # sockfd.setblocking(False)
    
    # 超时时间
    sockfd.settimeout(2)
    
    
    while True:
        print("Waiting from connect...")
        try:
            connfd,addr = sockfd.accept()
        except (BlockingIOError, timeout) as e:
            sleep(2)
            f.write("%s : %s
    "%(ctime(),e))
            f.flush()
        else:
            print("Connect from",addr)
            data = connfd.recv(1024).decode()
            print(data)
    非阻塞I/O

    I/O多路复用

    1. 定义: 同时监控多个IO事件,当哪个IO事件准备就绪就执行哪个IO事件。以此形成可以同时处理多个IO行为,避免一个IO阻塞造成其他IO均无法执行,提高了IO执行效率

    2. 具体方案
     
      * select方法 : windows linux unix
        
        rs, ws, xs=select(rlist, wlist, xlist[, timeout])
        功能: 监控IO事件,阻塞等待IO发生
        参数:rlist 列表 存放关注的等待发生的IO事件
           wlist 列表 存放关注的要主动处理的IO事件
              xlist 列表 存放关注的出现异常要处理的IO
           timeout 超时时间  --> 超时继续执行, 不报错
        返回值: rs 列表 rlist中准备就绪的IO  
             ws 列表 wlist中准备就绪的IO
               xs 列表 xlist中准备就绪的IO
      
    from socket import *
    from select import select
    
    s = socket()
    s.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
    s.bind(('0.0.0.0',8888))
    s.listen(5)
    
    rlist = [s]  
    wlist = []
    xlist = []
    
    while True:
        rs,ws,xs = select(rlist,wlist,xlist)    # rs列表 --> 里面是rlist中准备就绪的I/O
        for r in rs:
            print(type(r))
            if r is s:
                c,addr = r.accept()
                print("Connect from",addr)
                rlist.append(c) 
            else:
                # 表明有客户端发送消息
                data = r.recv(1024).decode()
                print(data)
                r.send(b'OK')
    select
     
         * poll方法: linux unix
          
     
        p = select.poll()
     
        功能 : 创建poll对象
        返回值: poll对象
     
        p.register(fd,event)
        功能: 注册关注的IO事件
        参数:fd 要关注的I/O
             event 要关注的IO事件类型
               常用类型:POLLIN 读IO事件(rlist)
                      POLLOUT 写IO事件 (wlist)
                      POLLERR 异常IO (xlist)
                      POLLHUP 断开连接
             e.g. p.register(sockfd,POLLIN|POLLERR)

        p.unregister(fd)
        功能:取消对IO的关注
        参数:IO对象或者IO对象的fileno
     
        events = p.poll()
        功能: 阻塞等待监控的IO事件发生
        返回值: 返回发生的IO
        events格式 [(fileno,event),()....]
        每个元组为一个就绪IO,元组第一项是该IO的fileno,第二项为该IO就绪的事件类型
    from socket import *
    from select import *
    
    # 创建监听套接字,作为关注的IO
    s = socket()
    s.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
    s.bind(('0.0.0.0',8888))
    s.listen(3)
    
    # 创建poll对象
    p = poll()
    
    # 建立查找字典,通过IO的fileno查找io对象
    # 始终与register的IO保持一直
    fdmap = {s.fileno(): s}
    
    # 关注 s
    p.register(s,POLLIN|POLLERR)
    
    # 循环监控IO发生
    while True:
        events = p.poll() # 阻塞等待IO发生
        # 循环遍历查看哪个IO准备就绪
        for fd,event in events:
            print(events)
            if fd == s.fileno():
                c,addr = fdmap[fd].accept()
                print("Connect from",addr)
                # 关注客户端连接套接字
                p.register(c,POLLIN|POLLHUP)
                fdmap[c.fileno()] = c  # 维护字典
            elif event & POLLIN:
                data = fdmap[fd].recv(1024).decode()
                if not data:
                    p.unregister(fd) # 取消监控
                    fdmap[fd].close()
                    del fdmap[fd] # 从字典删除
                    continue
                print(data)
                p.register(fdmap[fd],POLLOUT)
            elif event & POLLOUT:
                fdmap[fd].send(b'OK')
                p.register(fdmap[fd], POLLIN) # 每注册一次都会覆盖上次注册时选定的事件类型
    poll
      
        * epoll方法: linux
     
          使用方法 : 基本与poll相同
            * 生成对象改为 epoll()
            * 将所有事件类型改为EPOLL类型
    # 创建epoll对象
    ep = epoll()
    
    # 建立查找字典,通过IO的fileno查找io对象
    # 始终与register的IO保持一致
    fdmap = {s.fileno():s}
    
    # 关注 s
    ep.register(s,EPOLLIN|EPOLLERR)
    epoll

    3. 三种方案的比较

      select poll epoll
    适用的操作系统   Linux, Unix Linux
    效率 /
    监控I/O数量 /
    触发方式 /

     

    异步I/O

    协程

    1. 定义:纤程,微线程。是允许在不同入口点不同位置暂停或开始的计算机程序,简单来说,协程就是可以暂停执行的函数

    2. 协程原理 : 记录一个函数的上下文,协程调度切换时会将记录的上下文保存,在切换回来时进行调取,恢复原有的执行内容,以便从上一次执行位置继续执行。

    3. 协程优缺点
     
      优点
        1. 协程完成多任务占用计算资源很少
        2. 由于协程的多任务切换在应用层完成,因此切换开销少
        3. 协程为单线程程序,无需进行共享资源同步互斥处理

      缺点
        协程的本质是一个单线程,无法利用计算机多核资源

     

    4. Python对协程的支持

      python3.5以后,使用标准库asyncioasync/await 语法来编写并发代码。asyncio库通过对异步IO行为的支持完成python的协程。
      虽然官方说asyncio是未来的开发方向,但是由于其生态不够丰富,大量的客户端不支持awaitable需要自己去封装,所以在使用上存在缺陷。更多时候只能使用已有的异步库(asyncio等),功能有限.
    import asyncio   # 协程IO
    
    async def fun1():
        aaaaaaaaaa
        await asyncio.sleep(3)
        bbbbbbbbbb
    
    async def fun2():
        cccccccccc
        await
        ddddddddddd
    asyncio

         

        * 协程函数的定义需要使用关键字  --> async  def  func()
        * 只有当满足await条件时,才会从fun1中跳出,执行fun2的内容
        * 关于await条件  --> 只支持asyncio库中的内容(如asyncio.sleep()),  而不能支持time.sleep(),  accept(),  recv()等
        * 当我们使用常用的模块,而不是asyncio模块时,无法实现跳转,也就实现不了协程

    5. 第三方协程模块

      * greenlet模块

        安装 : sudo pip3 install greenlet
     
        函数:
          greenlet.greenlet(func)
          功能:创建协程对象
          参数:协程函数
     
          g.switch()
          功能:选择要执行的协程函数
    from greenlet import greenlet
    
    def fun1():
        print("执行 fun1")
        gr2.switch()
        print("结束 fun1")
        gr2.switch()
    
    def fun2():
        print("执行 fun2")
        gr1.switch()
        print("结束 fun2")
    
    # 将函数变为协程
    gr1 = greenlet(fun1)
    gr2 = greenlet(fun2)
    
    gr1.switch() # 选择执行的协程函数
    greenlet

      * gevent模块 

        安装:sudo pip3 install gevent

        函数
     
          gevent.spawn(func,argv)
          功能: 生成协程对象
          参数:func 协程函数
               argv 给协程函数传参(不定参)
          返回值: 协程对象

          gevent.joinall(list,[timeout])
          功能: 阻塞等待协程执行完毕  --> 是一个阻塞函数, list中所有的协程函数都执行完毕, 该函数才结束阻塞.
          参数:list 协程对象列表
               timeout 超时时间

          gevent.sleep(sec)
          功能: gevent睡眠阻塞
          参数:睡眠时间
     
    * *************************************************************************************** *
    * gevent协程只有在遇到gevent指定的阻塞行为时才会自动在协程之间进行跳转 *
    * 如gevent.joinall(),gevent.sleep()带来的阻塞                                                      *
    * *************************************************************************************** *

        monkey脚本

          作用:在gevent协程中,协程只有遇到gevent指定类型的阻塞才能跳转到其他协程.
                因此,我们希望将普通的IO阻塞行为转换为可以触发gevent协程跳转的阻塞,以提高执行效率。

          转换方法:gevent 提供了一个脚本程序monkey,可以修改底层解释IO阻塞的行为,将很多普通阻塞转换为gevent阻塞。

          使用方法
            【1】 导入monkey
                    from gevent import monkey

            【2】 运行相应的脚本,例如转换socket中所有阻塞
                    monkey.patch_socket()

            【3】 如果将所有可转换的IO阻塞全部转换则运行all
                    monkey.patch_all()
     
              import gevent
                from gevent import monkey
                dir(monkey)
                --->  查看有哪些patch_xxx()
     
            【4】 注意:脚本运行函数需要在对应模块导入前执行
    import gevent
    from gevent import monkey
    monkey.patch_time() # 修改对time模块中阻塞的解释行为
    from time import sleep
    
    # 协程函数
    def foo(a,b):
        print("Running foo ...",a,b)
        # gevent.sleep(3)
        sleep(3)
        print("Foo again..")
    
    def bar():
        print("Running bar ...")
        # gevent.sleep(2)
        sleep(2)
        print("Bar again..")
    
    # 生成协程对象
    f = gevent.spawn(foo,1,2)
    g = gevent.spawn(bar)
    
    gevent.joinall([f,g]) #阻塞等待f,g代表的协程执行完毕
    gevent

    协程是单线程, 生成了协程对象后, 只有当线程的后续执行中遇到了gevent阻塞, 协程函数才会执行, 否则协程函数永远不会执行.

    gevent遇到阻塞后, 哪个协程函数可以执行就执行哪个协程函数; 上面的代码中, foo函数是先创建的协程函数,遇到joinall阻塞后,先执行的是foo函数.

    import gevent
    from gevent import monkey
    monkey.patch_all() # 执行脚本,修改socket
    from socket import *
    
    def handle(c):
        while True:
            data = c.recv(1024).decode()
            if not data:
                break
            print(data)
            c.send(b'OK')
        c.close()
    
    # 创建tcp套接字
    s = socket()
    s.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
    s.bind(('0.0.0.0',8888))
    s.listen(5)
    
    # 循环接收来自客户端连接
    while True:
        c,addr = s.accept()
        print("Connect from",addr)
        # handle(c) # 处理具体客户端请求
        gevent.spawn(handle,c) # 协程方案
    gevent_server
  • 相关阅读:
    HTML中使用Vue+Dhtmlxgantt制作任务进度图
    Vue中使用js-xlsx导出Data数据到Excel
    Vue生命周期
    ajax调用免费的天气API
    maven无法自动下载依赖包settings.xml文件配置
    idea打开项目没有src目录
    java jdk idea maven安装及配置
    CondaHTTPError: HTTP 000 CONNECTION FAILED for url <https://conda.anaconda.org/pytorch
    The procedure entry point OPENSSL_sk_new_reserve could not be located in the dynamic link library
    Nuget打包没有注释显示
  • 原文地址:https://www.cnblogs.com/NeverYa/p/11367724.html
Copyright © 2011-2022 走看看