from yield to await——Python協程演進過程(二)

在之前的文章中我們了解了Duck Type、Iterator和Generators,知道了只要是在函數中出現yield關鍵字的函數就是個生成器函數,生成器函數的特點是,函數調用後會立刻返回一個生成器,該生成器擁有next方法,通過next方法獲得生成器的結果。同時,生成器這種惰性載入的特性,可以作為協程來用。

生產者消費者

生產者消費者模式是一個在並發領域常用的模式。在多線程中為了處理多個請求,我們往往會採用一個隊列型的數據結構去緩存請求,生產者就是發起請求的用戶,消費者就是從隊列中取出請求並執行的線程。在這裡一方或多方發起,一方消耗,稱為生產者消費者模式。

該模式的協程版本如下,consumer裡面通過一個while循環接收yield處的生產者發送,併當發送為None時跳出循環,結束協程。

def consumer():n while True:n n = yieldn if not n:n breakn print([CONSUMER]Consuming %s... % n)nndef producer(c):n next(c)n for n in range(1, 5):n print([PRODUCER]Producing %s... % n)n c.send(n)n c.close()nnc = consumer()nproducer(c)nn# [PRODUCER]Producing 1...n# [CONSUMER]Consuming 1...n# [PRODUCER]Producing 2...n# [CONSUMER]Consuming 2...n# [PRODUCER]Producing 3...n# [CONSUMER]Consuming 3...n# [PRODUCER]Producing 4...n# [CONSUMER]Consuming 4...n

預激生成器

這個程序有個要注意的一點是,在producer中消費協程時,需要先執行一步next,使得協程推進到第一個yield處,不然yield在消費時是沒法產出值的。這個和協程所處的狀態有關,在inspect模塊中getgeneratorstate這個方法可以獲取協程當前狀態。

GEN_GREATED —— 等待開始執行。nGEN_RUNNING —— 解釋器正在執行。nGEN_SUSPENEDE —— 在yield表達式處暫停。nGEN_CLOSED —— 執行結束

基於最小驚訝原則,這個預激動作是被建議隱藏的。預激生成器可以用一個裝飾器來實現,那麼我們就可以通過在裝飾器中提前執行一個next,隱藏該動作。代碼就變成了下面這個樣子。

from functools import wrapsndef coroutine(fun):n @wraps(fun)n def primer(*args, **kwargs):n gen = fun(*args, **kwargs)n next(gen)n return genn return primernn@coroutinendef consumer():n ...nndef producer(c):n for n in range(1, 5):n print([PRODUCER]Producing %s... % n)n c.send(n)n c.close()nnc = consumer()nproducer(c)n

返回值

凡是函數中出現了yield關鍵詞,這個函數就被認為是個生成器函數。同時,這個生成器函數在返回時是立刻返回一個生成器而不是函數運行的結果,該函數返回結果得用拋出異常的方式返回。

接下來改寫這個生產者消費者,使其返回生產者發送的值的累加的結果,新增的主要代碼加了注釋「#<-」,最終的返回值通過發送一個None,並獲取StopIteration異常中的值返回。

...n@coroutinendef consumer():n sum_ = 0n while True:n n = yieldn if not n:n breakn sum_ = sum_ + n#<-n print([CONSUMER]Consuming %s... % n)n return sum_#<-nndef producer(c):n for n in range(1, 5):n print([PRODUCER]Producing %s... % n)n c.send(n)n try:n c.send(None)#<-n except StopIteration as exc:n print("[PRODUCER]Producing GET",exc.value)nnproducer(consumer())n#...n#[PRODUCER]Producing GET 10n

這樣 我們用yield實現了一個可用的生產者消費者。但這種編碼方式挺彆扭的,而且,當需要處理大量異常的時候,這種編碼方式就會變得極其臃腫,協程這一寫法本來就是為了解決Callback Hell,這樣處理起來並沒有多省事。雖然有gevent、tornado等庫來幫我們處理這些瑣事,但如果有個原生的且好用的語法,就會好得多。因此,yeild from 語法,就在隨後推出了,而下一part會專講這個語法。

from yield to await--Python協程演進過程(一)

from yield to await--Python協程演進過程(二)

from yield to await--Python協程演進過程(三)

python雜七雜八的使用經驗 - 知乎專欄

推薦閱讀:

關於go語言協程調度的一個問題(具體請看問題描述)?
協程調度時機三:搶佔式調度
協程初步筆記

TAG:Python | 协程 | 并发 |