zoukankan      html  css  js  c++  java
  • 异步IO:asyncio

      asyncio

      aysncio是Python3.4版本引入的标准库,直接内置了对异步IO的支持。

      asyncio的编程模式就是一个消息循环。我们从asyncio模板中直接获取一个Eventloop(事件循环)的引用,然后把需要执行的协程扔到Eventloop中执行,就实现了异步IO。

      用asyncio实现Hello world代码如下

      async_hello.py

    # asyncio start
    import asyncio
    # 装饰器装饰的函数为协程函数
    @asyncio.coroutine
    def hello():
        print('Hello world!')
        r = yield from asyncio.sleep(1)
        print('Hello again!')
    
    # 获取Eventloop事件循环
    loop = asyncio.get_event_loop()
    # 执行协程,协程函数不能直接执行,需要放入事件循环才能执行
    loop.run_until_complete(hello())
    # 关闭事件循环
    loop.close()
    # asyncio end
    

      输出如下

    Hello world!
    Hello again!
    

      解析:

      运行事件循环,事件循环绑定的协程函数是hello()该协程函数首先打印 “Hello world! ” 然后遇到yield from asyncio.sleep(1)模拟的IO耗时操作,此时如果事件循环绑定了其他协程则会去执行其他协程,本次只绑定了一个协程所以等待1秒后输出“hello again!”

      @asyncio.coroutine把一个generator标记为coroutine类型,然后,我们就把这个coroutine扔到EventLoop中执行。

      hello()会首先打印出Hello world!,然后,yield from语法可以让我们方便地调用另一个generator。由于asyncio.sleep()也是一个coroutine,所以线程不会等待asyncio.sleep(),而是直接中断并执行下一个消息循环。当asyncio.sleep()返回时,线程就可以从yield from拿到返回值(此处是None),然后接着执行下一行语句。

      把asyncio.sleep(1)看成是一个耗时1秒的IO操作,在此期间,主线程并未等待,而是去执行EventLoop中其他可以执行的coroutine了,因此可以实现并发执行。

      我们用Task封装两个coroutine试试:

    # 封装两个task任务 start
    import asyncio
    import threading
    # 装饰器装饰的函数为协程函数
    @asyncio.coroutine
    def hello():
        print('Hello world! %s' % threading.currentThread() )
        yield from asyncio.sleep(1)
        print('Hello again! %s' % threading.currentThread())
    
    # 获取Eventloop事件循环
    loop = asyncio.get_event_loop()
    # 执行协程,协程函数不能直接执行,需要放入事件循环才能执行
    tasks = [hello(),hello()]
    loop.run_until_complete(asyncio.wait(tasks))
    # 关闭事件循环
    loop.close()
    # 封装两个task任务 end
    

      输出如下

    Hello world! <_MainThread(MainThread, started 18168)>
    Hello world! <_MainThread(MainThread, started 18168)>
    等待约1秒
    Hello again! <_MainThread(MainThread, started 18168)>
    Hello again! <_MainThread(MainThread, started 18168)>
    

      由打印的当前线程名称可以看出,两个coroutine是由同一个线程并发执行的。所以输出两个Hello world!几乎是同时的,然后两个协程同时等待1秒输出两个Hello again! 。如果不是并发则应该前后两次输出,类似下面这样

    Hello world! <_MainThread(MainThread, started 18168)>
    等待约1秒
    Hello again! <_MainThread(MainThread, started 18168)>
    Hello world! <_MainThread(MainThread, started 18168)>
    等待约1秒
    Hello again! <_MainThread(MainThread, started 18168)>
    

      如果把asyncio.sleep()换成真正的IO操作,则多个coroutine是由同一个线程并发执行的。

      我们用asyncio的异步网络连接来获取sina,sohu和163的网站首页

      async_wget.py

    import asyncio
    
    @asyncio.coroutine
    def wget(host):
        print('wget %s...' % host)
        # 创建TCP客户端并连接服务器,或者说创建一个TCP连接对象
        # open_connection接收两个参数:主机名和端口号
        # connect是协程,这步仅创建协程对象,立即返回,不阻塞
        connect = asyncio.open_connection(host,80)
        # 运行协程连接服务器,这步是阻塞操作,释放CPU
        # 连接创建成功后,asyncio.open_connection方法返回的就是读写对象
        # 读写对象分别为 StreamReader和StreamWriter实例
        # 它们也是协程对象,底层调用Socker模块的send和recv方法实现读写
        reader,writer = yield from connect
        # heade是发送给服务器的消息,意为获取页面的header信息
        # 它的格式是固定的
        header = 'GET / HTTP/1.0
    Host: %s
    
    ' %host
        # 给服务器发消息,注意消息是二进制的,本次使用utf-8编码
        writer.write(header.encode('utf-8'))
        # writer.drain()是一个与底层IO输入缓冲区交互流量控制方法,需要配合writer.write使用
        # 当缓冲区达到上限时,drain()阻塞,待缓冲区回落到下限时,写操作恢复
        # 当不需要等待时,drain()会立即返回,假如上面的消息内容较少,不会阻塞
        # 这就是一个控制消息数据量的控制阀
        yield from writer.drain()
        # 给服务器发送消息后,就等着读取服务器返回来的消息
        while True:
            # 读取数据是阻塞操作,释放CPU
            # reader相当于一个水盆,服务器发来的数据是水流
            # readline表示读取一行,以
    作为换行符
            # 如果在出现
    之前,数据流出现EOF(End of File文件结束符)也会返回
            # 相当于出现
    或EOF时,拧上水龙头,line就是这盆水
            line = yield from reader.readline()
            # 数据接收完毕,会返回空字符串
    ,退出while循环,结束数据接收
            if line == b'
    ':
                break
            # 接收数据是二进制,转换为UTF-8格式并打印
            # rstrip()方法删掉字符串结尾就是右边的空白字符,也就是
    
            print('%s header > %s' % (host,line.decode('utf-8').rstrip()))
        # 关闭数据流,可以省略
        writer.close()
        yield from writer.wait_closed()
    
    hosts = ['192.168.1.100']
    hosts = ['www.sina.com.cn','www.sohu.com','www.163.com']
    # 创建task任务
    tasks = [wget(host) for host in hosts ]
    # 创建事件循环
    loop = asyncio.get_event_loop()
    # 运行事件循环
    loop.run_until_complete(asyncio.wait(tasks))
    

      执行结果如下

    wget www.sohu.com...
    wget www.163.com...
    wget www.baidu.com...
    www.sohu.com header > HTTP/1.1 307 Temporary Redirect
    www.sohu.com header > Content-Type: text/html
    www.sohu.com header > Content-Length: 180
    www.sohu.com header > Connection: close
    www.sohu.com header > Server: nginx
    www.sohu.com header > Date: Fri, 29 Oct 2021 09:18:16 GMT
    www.sohu.com header > Location: https://www.sohu.com/
    www.sohu.com header > FSS-Cache: from 4094608.6191770.5431472
    www.sohu.com header > FSS-Proxy: Powered by 4356756.6716062.5693624
    www.163.com header > HTTP/1.1 301 Moved Permanently
    www.163.com header > Date: Fri, 29 Oct 2021 09:18:16 GMT
    www.163.com header > Content-Length: 0
    www.163.com header > Connection: close
    www.163.com header > Server: web cache
    www.163.com header > Location: https://www.163.com/
    www.163.com header > Cache-Control: no-cache,no-store,private
    www.163.com header > cdn-user-ip: 116.25.237.202
    www.163.com header > cdn-ip: 183.47.233.150
    www.163.com header > X-Cache-Remote: MISS
    www.163.com header > cdn-source: baishan
    www.baidu.com header > HTTP/1.0 200 OK
    www.baidu.com header > Accept-Ranges: bytes
    www.baidu.com header > Cache-Control: no-cache
    www.baidu.com header > Content-Length: 14615
    www.baidu.com header > Content-Type: text/html
    www.baidu.com header > Date: Fri, 29 Oct 2021 09:18:16 GMT
    www.baidu.com header > P3p: CP=" OTI DSP COR IVA OUR IND COM "
    www.baidu.com header > Pragma: no-cache
    www.baidu.com header > Server: BWS/1.1
    www.baidu.com header > Set-Cookie: BAIDUID=00D297D9A818428E9A332BB01C2BA52E:FG=1; expires=Thu, 31-Dec-37 23:55:55 GMT; max-age=2147483647; path=/; domain=.baidu.com  
    www.baidu.com header > Set-Cookie: BIDUPSID=00D297D9A818428E9A332BB01C2BA52E; expires=Thu, 31-Dec-37 23:55:55 GMT; max-age=2147483647; path=/; domain=.baidu.com      
    www.baidu.com header > Set-Cookie: PSTM=1635499096; expires=Thu, 31-Dec-37 23:55:55 GMT; max-age=2147483647; path=/; domain=.baidu.com
    www.baidu.com header > Set-Cookie: BAIDUID=00D297D9A818428E1A7073DA9DEB891D:FG=1; max-age=31536000; expires=Sat, 29-Oct-22 09:18:16 GMT; domain=.baidu.com; path=/; version=1; comment=bd
    www.baidu.com header > Traceid: 1635499096061364455412067686187092680246
    www.baidu.com header > Vary: Accept-Encoding
    www.baidu.com header > X-Frame-Options: sameorigin
    www.baidu.com header > X-Ua-Compatible: IE=Edge,chrome=1
    PS D:learn-python3函数式编程> & C:/ProgramData/Anaconda3/python.exe d:/learn-python3/异步IO/async_wget.py
    d:/learn-python3/异步IO/async_wget.py:4: DeprecationWarning: "@coroutine" decorator is deprecated since Python 3.8, use "async def" instead
      def wget(host):
    wget www.163.com...
    wget www.sohu.com...
    wget www.sina.com.cn...
    www.sohu.com header > HTTP/1.1 307 Temporary Redirect
    www.sohu.com header > Content-Type: text/html
    www.sohu.com header > Content-Length: 180
    www.sohu.com header > Connection: close
    www.sohu.com header > Server: nginx
    www.sohu.com header > Date: Fri, 29 Oct 2021 09:19:33 GMT
    www.sohu.com header > Location: https://www.sohu.com/
    www.sohu.com header > FSS-Cache: from 4160145.6322843.5497010
    www.sohu.com header > FSS-Proxy: Powered by 4356756.6716062.5693624
    www.163.com header > HTTP/1.1 301 Moved Permanently
    www.163.com header > Date: Fri, 29 Oct 2021 09:19:33 GMT
    www.163.com header > Content-Length: 0
    www.163.com header > Connection: close
    www.163.com header > Server: web cache
    www.163.com header > Location: https://www.163.com/
    www.163.com header > Cache-Control: no-cache,no-store,private
    www.163.com header > cdn-user-ip: 116.25.237.202
    www.163.com header > cdn-ip: 183.47.233.148
    www.163.com header > X-Cache-Remote: MISS
    www.163.com header > cdn-source: baishan
    www.sina.com.cn header > HTTP/1.1 302 Found
    www.sina.com.cn header > Server: Tengine
    www.sina.com.cn header > Date: Fri, 29 Oct 2021 09:19:33 GMT
    www.sina.com.cn header > Content-Type: text/html
    www.sina.com.cn header > Content-Length: 242
    www.sina.com.cn header > Connection: close
    www.sina.com.cn header > Location: https://www.sina.com.cn/
    www.sina.com.cn header > X-DSL-CHECK: 5
    www.sina.com.cn header > X-Via-CDN: f=alicdn,s=cache5.cn1366,c=116.25.237.202;
    www.sina.com.cn header > Via: cache5.cn1366[,0]
    www.sina.com.cn header > Timing-Allow-Origin: *
    www.sina.com.cn header > EagleId: 0e1d289916354991731167720e
    

      可见3个连接由一个线程通过coroutine并发完成。

      小结

      asyncio提供了完善的异步IO支持;

      异步操作需要在coroutine中通过yield from完成;

      多个coroutine可以封装成一组Task然后并发执行。

  • 相关阅读:
    image 和 barplot 的组合
    par函数mgp 参数-控制坐标轴的位置
    R语言绘图时的边界碰撞问题
    R语言绘制花瓣图flower plot
    mothur 计算稀释性曲线
    R语言 vegan包计算物种累计曲线
    R语言数据框小技巧
    tophat-fusion 鉴定融合基因
    FusionCancer-人类癌症相关的融合基因的数据库
    rrnDB数据库简介-16S基因多拷贝数的证据
  • 原文地址:https://www.cnblogs.com/minseo/p/15481459.html
Copyright © 2011-2022 走看看