異步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\r\nHost: %s\r\n\r\n' %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表示讀取一行,以\n作為換行符
        # 如果在出現\n之前,數據流出現EOF(End of File文件結束符)也會返回
        # 相當於出現\n或EOF時,擰上水龍頭,line就是這盆水
        line = yield from reader.readline()
        # 數據接收完畢,會返回空字符串\r\n,退出while循環,結束數據接收
        if line == b'\r\n':
            break
        # 接收數據是二進制,轉換為UTF-8格式並打印
        # rstrip()方法刪掉字符串結尾就是右邊的空白字符,也就是\n
        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然后並發執行。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM