python異步IO編程(一)
基礎概念
協程:python ?generator與coroutine
異步IO (async IO):一種由多種語言實現的與語言無關的范例(或模型)。
asyncio:Python 3.4版本引入的標準庫,直接內置了對異步IO的支持。
?
異步IO
線程,多線程
多線程善于處理I/O密集型任務。
多進程擅長處理計算密集型(CPU-bound)任務:強密集循環和數學計算都屬于此類。
并發是并行的一種特殊類型(或者說子類),多線程是并發的表現形式,多進程是并行的表現形式。
Python通過它的包 multiprocessing,threading 和 concurrent.futures 已經對這兩種形式都提供了長期的支持。
?
異步IO
異步IO是一種單進程、單線程的設計:它使用協同多任務處理機制,是以協程為核心的一種編程模型。
異步IO并不是新發明的概念,它已經存在或正在被構建到其他語言及運行時環境中,如 Go,C# 和 Scala 等。
異步IO模型異步IO采用消息循環的模式,在消息循環中,主線程不斷地重復“讀取消息-處理消息”這一過程:
loop = get_event_loop() // 實例消息隊列 while True: event = loop.get_event() // 從隊列中讀取消息 process_event(event) //處理消息消息模型其實早在應用在桌面應用程序中了。一個GUI程序的主線程就負責不停地讀取消息并處理消息。所有的鍵盤、鼠標等消息都被發送到GUI程序的消息隊列中,然后由GUI程序的主線程處理。
由于GUI線程處理鍵盤、鼠標等消息的速度非常快,所以用戶感覺不到延遲。某些時候,GUI線程在一個消息處理的過程中遇到問題導致一次消息處理時間過長,此時,用戶會感覺到整個GUI程序停止響應了,敲鍵盤、點鼠標都沒有反應。
這種情況說明在消息模型中,處理一個消息必須非常迅速,否則,主線程將無法及時處理消息隊列中的其他消息,導致程序看上去停止響應。
消息模型是如何解決同步IO必須等待IO操作這一問題的呢?當遇到IO操作時,代碼只負責發出IO請求,不等待IO結果,然后直接結束本輪消息處理,進入下一輪消息處理過程。當IO操作完成后,將收到一條“IO完成”的消息,處理該消息時就可以直接獲取IO操作結果。
在“發出IO請求”到收到“IO完成”的這段時間里,同步IO模型下,主線程只能掛起,但異步IO模型下,主線程并沒有休息,而是在消息循環中繼續處理其他消息。這樣,在異步IO模型下,一個線程就可以同時處理多個IO請求,并且沒有切換線程的操作。對于大多數IO密集型的應用程序,使用異步IO將大大提升系統的多任務處理能力。
?
?
asyncio
async與asyncio.coroutine
在 python3.5 中,創建一個協程僅僅只需使用 async 關鍵字,而python3.4使用 @asyncio.coroutine 裝飾器。都引入了原生協程或者說異步生成器。下面的任一代碼,都可以作為協程工作,形式上也是等同的:
import asyncio async def ping_server(ip): # 3.5 pass @asyncio.coroutine def load_file(path): # 3.4 pass
?
yield from與await
3.1 中協程操作只是簡單的生成器調用,常見的我們還需要在生成器或者說協程之間相互調用,用到yield from。yield from 用于一個generator調用另一個generator,主要是為了generator之間的調用。
yield from 表達式的使用方式如下:
import asyncio @asyncio.coroutine def get_jason(client, url): file_content = yield from load_file( ' /Usrs/scott/data.txt ' )
?
協程的一個關鍵特性是它們可以被鏈接到一起。(記住,一個協程是可等待的,所以另一個協程可以使用?
await
?來等待它。)await 將控制器傳遞給時間循環。(掛起當前運行的協程與yield from類似),使用方式如下:
async def ping_local(ip): return await ping_server( ' 192.168.1.1 ' )
Python3.5 對這兩種調用協程的方法都提供了支持,但是推薦 async/await 作為首選。
Python執行的時候, g() 函數范圍內如果遇到表達式 await f(),就是 await 在告訴事件循環“掛起 g() 函數,直到 f() 返回結果,在此期間,可以運行其他函數。”
async def g(): # 暫停,直到 f()結束再回到g() r = await f() return r
當你使用 await f() 時,要求 f() 是一個可等待的對象。但這并沒有什么用。現在,只需要知道可等待對象要么是(1)其他的協程,要么就是(2)定義了 .await() 函數且返回迭代器的對象。如果你正在編寫程序,絕大多數情況只需要關注案例#1。
使用規則
1. 使用 await 與 return 的組合創建協程函數。想要調用一個協程函數,必須使用 await 等待返回結果。
2. 在 async def 代碼塊中使用 yield 的情況并不多見(只有Python的近期版本才可用)。當你使用 async for 進行迭代的時候,會創建一個異步生成器。暫時先忘掉異步生成器,將目光放在使用 await 與 return 的組合創建協程函數的語法上。
3. 在任何使用 async def 定義的地方都不可以使用 yield from,這會引發異常 SyntaxError。
4. 一如在 def 定義的函數之外使用 yield 會引發異常 SyntaxError,在 async def 定義的協程之外使用 await 也會引發異常 SyntaxError。你只能在協程內部使用 await。
?
Event Loop
asyncio的編程模型就是一個消息循環。我們從asyncio模塊中直接獲取一個EventLoop的引用,然后把需要執行的協程扔到EventLoop中執行,就實現了異步IO。
用asyncio實現Hello world代碼如下:
import asyncio @asyncio.coroutine def hello(): print ( " Hello world! " ) # 異步調用asyncio.sleep(1): r = yield from asyncio.sleep(1 ) print ( " Hello again! " ) # 獲取EventLoop: loop = asyncio.get_event_loop() # 執行coroutine loop.run_until_complete(hello()) loop.close()
@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了,因此可以實現并發執行。
async/await版本:
import asyncio async def hello(): print ( " Hello World " ) r = await asyncio.sleep(1 ) print ( " Again " ) loop = asyncio.get_event_loop() loop.run_until_complete(hello()) loop.close()
我們用Task封裝兩個coroutine試試:
import threading import asyncio @asyncio.coroutine def hello(): print ( ' Hello world! (%s) ' % threading.currentThread()) yield from asyncio.sleep(1 ) print ( ' Hello again! (%s) ' % threading.currentThread()) loop = asyncio.get_event_loop() tasks = [hello(), hello()] loop.run_until_complete(asyncio.wait(tasks)) loop.close()
觀察執行過程:
Hello world! (<_MainThread(MainThread, started 140735195337472)>)
Hello world! (<_MainThread(MainThread, started 140735195337472)>)
(暫停約1秒)
Hello again! (<_MainThread(MainThread, started 140735195337472)>)
Hello again! (<_MainThread(MainThread, started 140735195337472)>)
由打印的當前線程名稱可以看出,兩個coroutine是由同一個線程并發執行的。
如果把asyncio.sleep()換成真正的IO操作,則多個coroutine就可以由一個線程并發執行。
?
?
參考:
https://mp.weixin.qq.com/s/fJaXmfHfYEk6XL2y8NmKmQ
https://www.jianshu.com/p/2dfaacdd0a90
?
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
