python yield 與 協程的實現
今天早上偶然看到一篇文章《PHP如何實現協程》,頓時驚呆了!PHP什麼時候這麼強了?那我還寫Go 做什麼?仔細閱讀文章發現php使用的是yield關鍵實現的,心想這個不就是py裡的生成器嗎?雖然我不知道PHP裡的yield和py的有什麼區別,由於之前並沒有深入瞭解過著玩意所以藉機會了解一下!
我們都知道函式(子例程)的控制權要等到return 後才會交給呼叫者,函式中的變數隨著控制權結束後也就消失了,需要等到下次呼叫重新開始,然而yield 就是這麼神奇可以執行到yield 關鍵字處然後將控制權交給呼叫者,然後在次進入函式從上次結束的地方繼續執行不斷迴圈
先從一個小需求入手:
求出一個數字列表中的素數
import math def get_primes(input_list): return (element for element in input_list if is_prime(element)) def is_prime(number): if number > 1: if number == 2: return True if number % 2 == 0: return False for current in range(3,int(math.sqrt(number)+1),2): if number % current == 0: return False return True return False nums = [1,2,3,4,5,6,7,8,9] list = get_primes(nums) for i in list: print(i)
這個需求很簡單就實現了~~·
假如現在萬惡的產品經理想要求無限大的素數
那麼你可能會想我直接從一個非常的大的數開始算不行麼?可以的!但是就是有點LOW
如果我們往list存無限個數字 就更加不可能了
這是我們的yield就非常有作用了,上面說了遇到yield 會將交出控制權,然後進入函式繼續執行
這裡我們改造一下get_primes 函式
def get_primes_v2(number): while True: time.sleep(2) print("*******", number) if is_prime(number): yield number number+=1
使用while True不停的去執行並且每次到yield 關鍵字處就將結果返回並且儲存函式的內部的狀
態,實現每次number+1 的效果這就是yield 的奇妙之處
接下來看看如何使用yield 實現協程(來自廖雪峰老師的例子)
import time
def consumer():
r = ''
while True:
n = yield r
if not n:
return
print('[CONSUMER] Consuming %s...' % n)
time.sleep(1)
r = '200 OK'
def produce(c):
c.__next__()
n = 0
while n < 5:
n = n + 1
print('[PRODUCER] Producing %s...' % n)
r = c.send(n)
print("send.......")
print('[PRODUCER] Consumer return: %s' % r)
c.close()
c = consumer()
produce(c)
注意到consumer函式是一個generator(生成器),把一個consumer傳入produce後:
首先呼叫c.next()啟動生成器;
然後,一旦生產了東西,通過c.send(n)切換到consumer執行;
consumer通過yield拿到訊息,處理,又通過yield把結果傳回;
produce拿到consumer處理的結果,繼續生產下一條訊息;
produce決定不生產了,通過c.close()關閉consumer,整個過程結束。
整個流程無鎖,由一個執行緒執行,produce和consumer協作完成任務,所以稱為“協程”,而非執行緒的搶佔式多工。
其實通過yield 也知道go 中goroutine 翻譯成協程還是有很大的不同的,goroutine 和協程是根本上不同的東西,之後再探討go 中的goroutine 是如何實現的