1. python yield與async/await
要點
最重要的是生成器函數(shù)碰到y(tǒng)ield停止執(zhí)行,收到next或send才會繼續(xù)執(zhí)行的機制。
而且send方法令我們可以傳遞值到生成器暫停的地方。
生成器執(zhí)行結(jié)束拋出StopIteration 異常。
yield from用于把其他生成器當做子例程調(diào)用。
MUST: yield from + iterable
MUST: @asyncio.coroutines + yield from / yield
MUST: await + coroutine / object with await() method (that must ruturn a iterable instead of a coroutine)
MUSTN'T: async def + yield/yield from
圖說
- 以下兩張圖中是6組代碼,實現(xiàn)了同樣的控制程序執(zhí)行順序的功能,調(diào)度器一樣,輸出也完全一樣,幾個小的演變一定程度上展示了yield和async/await的關(guān)系。


-
下面這張圖是用python內(nèi)部的審查函數(shù)來判斷類型,查看上圖中的@coroutine+yield構(gòu)成的函數(shù)到底在python內(nèi)部被當做什么
@coroutine+yield到底是什么
2. async/await 實驗
- 這是一系列關(guān)于await,async特性的小實驗,該新特性在PEP-0492中描述并在Python3.5中實現(xiàn)。是一個相當不錯的便于初學(xué)者理解await,async的資源。
- 在這里我翻譯了作者的綱要并闡述了自己的理解,不當之處歡迎指正。
- 代碼參看github/awaitexp。
實驗一:最簡調(diào)度器
目的是有一個基本上最簡單可實現(xiàn)的協(xié)程調(diào)度器。它與標準庫中asyncio模塊中相對復(fù)雜的調(diào)度器并列。
首先創(chuàng)建一個最簡單的基于生成器的協(xié)程函數(shù)A
@types.coroutine
def switch():
yield
然后它可以被其他用async def定義的的協(xié)程函數(shù)B和C await,只有當await返回時,B和C才繼續(xù)執(zhí)行。
這樣我們就可以有效地控制B和C的執(zhí)行順序。
然后我們創(chuàng)建了一個調(diào)度器,它對列表進行了兩次深拷貝以避免問題。它循環(huán)協(xié)程隊列,使用send方法對每個協(xié)程依次遞進,如果有協(xié)程已經(jīng)完成則將其移出隊列,當列表中的協(xié)程全部完成時結(jié)束。
實驗二:具有睡眠功能的簡單調(diào)度器
目的是給我們的調(diào)度器添加一個
awaitable的睡眠協(xié)程函數(shù)。盡管睡眠功能本身是相當簡單的,這還是給調(diào)度器增加了一定的復(fù)雜度。之后該調(diào)度器的睡眠功能是否能簡易地與其他事件組合將是一個有趣的問題。我們在第一個實驗的基礎(chǔ)上首先修改switch函數(shù)使其yield返回輸入給它的參數(shù)的字典,并且添加一個新的調(diào)用它的協(xié)程函數(shù)。
async def sleep(delay):
await switch(delay=delay)
然后通過args=coro.send(None)與該函數(shù)碰撞,得到含有delay參數(shù)的字典作為send的返回值。便可以判斷出是否調(diào)用調(diào)度器的睡眠機制。
最后在調(diào)度器中實現(xiàn)每一次協(xié)程列表循環(huán)結(jié)束后判斷在睡眠列表中的協(xié)程是否有到時間的,到時間或時間超出則添加到運行協(xié)程列表中進入循環(huán)執(zhí)行。如果運行列表中的協(xié)程都執(zhí)行完了,則查看睡眠列表中的協(xié)程中還需睡眠的最少時間,線程睡眠,睡眠完成再將其添加到運行隊列。
實驗三:使能增加新的協(xié)程
當前只有在開始指定的協(xié)程能被調(diào)度。該實驗的目的是允許新的協(xié)程能夠在調(diào)度器開始運行后被加入。
在當前實驗中我們拓展調(diào)度器為一個類并持有自己的協(xié)程隊列,并提供一個添加協(xié)程進入隊列的方法。然后獲取調(diào)度器的唯一實例,在定義的協(xié)程中使用實例方法來加入新的協(xié)程,再把該協(xié)程加入調(diào)度,這樣就實現(xiàn)了在運行中的某一刻加入新的協(xié)程。
實驗四:和線程池進行交互
有時我們會有一些計算(或者I/O)任務(wù),它們最好能在背景中執(zhí)行,而不是在主線程中。這個實驗展示了一個示例,通過線程池執(zhí)行器和被調(diào)度的協(xié)程交互來獲得背景計算。從長遠看,這個技術(shù)很可能不是一個良好的基礎(chǔ),因為它不能同時伺服IO選擇器和線程隊列。
在這一歩實驗中我們主要是添加了兩個部分,第一部分是一個裝飾器:
def background(fn):
@wraps(fn)
async def wrapper(*args,**kwargs):
return await switch(op='background',fn=fn,args=args,kwargs=kwargs)
return wrapper
該裝飾器能將一個比較耗時的計算函數(shù)封裝為一個協(xié)程,使其可以被其他協(xié)程await。在調(diào)度器中利用send函數(shù)的返回值可以獲取它的類型為background、函數(shù)入口地址以及函數(shù)的傳參,然后在調(diào)度器中按相應(yīng)機制執(zhí)行。
第二部分是在調(diào)度器中的修改:我們讓調(diào)度器類擁有了一個私有的concurrent.futures.ThreadPoolExecutor()對象。并在運行協(xié)程隊列的循環(huán)判斷中將background類型的操作提交給線程池對象,并將當前的協(xié)程移出運行隊列,添加到futures隊列中。然后在每次運行隊列循環(huán)后判斷futures中的任務(wù)是否有完成的(使用的參數(shù)為一旦有任一任務(wù)完成或被取消都返回),如果主線程此時處于將要睡眠的狀態(tài),就等待相應(yīng)的時間,沒有的話則立刻返回,下次再查詢,完成的任務(wù)將其所在協(xié)程帶入運行隊列,任務(wù)結(jié)果通過調(diào)度器send傳回該協(xié)程。
實驗五:
協(xié)程的一個典型應(yīng)用就是和異步I/O交互。該實驗的目的是讓調(diào)度器和Python的selector模型進行交互并提供一個基本的I/O模型。
類似于實驗四,我們在實驗三的基礎(chǔ)上讓調(diào)度器擁有了一個私有的
selectors.DefaultSelector()對象。創(chuàng)建了一個名為io輔助協(xié)程,供其他協(xié)程調(diào)用并且給調(diào)度器提供類型和操作信息。同樣利用主線程睡眠時間來等待selector的select方法。關(guān)鍵語句:for key, events in self.selector.select(timeout=timeout): ...,select返回在超時時間內(nèi)IO準備好的對象列表。
此外一部分是創(chuàng)建服務(wù)端和客戶端的協(xié)程。
在服務(wù)端的server協(xié)程函數(shù)中首先初始化socket,然后在主循環(huán)中awaitio操作(服務(wù)器本身是一個協(xié)程!),利用io輔助協(xié)程來等待io資源,取得連接后調(diào)用echo函數(shù)處理該連接,并將其加入調(diào)度器。在echo協(xié)程中,同樣利用io輔助協(xié)程異步獲取讀寫權(quán)限。循環(huán)直到break結(jié)束關(guān)閉鏈接。
對于客戶端測試程序,需要另外獲取一個調(diào)度器。定義一個echoclient協(xié)程函數(shù),利用io輔助協(xié)程異步發(fā)送接收信息。最后將所有測試協(xié)程加入調(diào)度器列表,開始執(zhí)行。

實驗六:
目的是有效地合并實驗四和實驗五的特性。這將使用標準的
self-pipe(實際上是一個self-socket)技巧來通知背景中復(fù)雜計算方法的完成。在服務(wù)端的
echo協(xié)程函數(shù)中添加了await add(3, 5)語句,add函數(shù)中添加sleep代表耗時的背景計算。在調(diào)度器類中添加了自己的一對socket。當有復(fù)雜計算時,添加入futures隊列,同時設(shè)置future完成回調(diào)函數(shù),該回調(diào)函數(shù)向自己的socket發(fā)送提示完成的消息。在select方法時判斷是否有準備好的io描述符屬于自己擁有的socket,如果有就意味著一個計算的完成,然后清空socket緩存,移除相應(yīng)的futures。

相關(guān)知識點
- selectors – High-level I/O multiplexing 基于select模塊原語
- file object - File object指一個對于底層資源暴露了面向文件API(擁有read和write方法)的對象。一個文件對象會被調(diào)制到訪問一個真實的磁盤文件或者其他類型的存儲或通信設(shè)備。它也被叫做類文件對象或者流。實際上有三類文件對象:原生二進制文件,緩沖二進制文件以及文本文件。它們的接口被定義在io模塊中。創(chuàng)建一個文件對象的典型方法是使用open方法。
