與子程序(或者說函數(shù))一樣,協(xié)程(coroutine)也是一種程序組件。Donald Knuth 曾說,子程序是協(xié)程的特例。
一個子程序就是一次函數(shù)調(diào)用,它只有一個入口,一次返回,調(diào)用順序是明確的。但協(xié)程的調(diào)用和子程序則大不一樣,協(xié)程允許有多個入口對程序進行中斷、繼續(xù)執(zhí)行等操作。
Python2 可以通過 yield 來實現(xiàn)基本的協(xié)程,但不夠強大,第三方庫 gevent 對協(xié)程提供了強大的支持。另外,Python3.5 提供了 async/await 語法來實現(xiàn)對協(xié)程的支持。本文只討論通過 yield 來實現(xiàn)協(xié)程。
對于經(jīng)典的生產(chǎn)者-消費者模型,如果用多線程來實現(xiàn),我們就需要一個線程寫消息,一個線程讀消息,而且需要鎖機制來避免對共享資源的訪問沖突。
相比多線程,協(xié)程的一大特點就是它在一個線程內(nèi)執(zhí)行,既避免了多線程之間切換帶來的開銷,也避免了對共享資源的訪問沖突。
下面,讓我們看看怎么用 yield 來實現(xiàn)簡單的生產(chǎn)者-消費者模型。
import time
def consumer():
message = ''
while True:
n = yield message # yield 使函數(shù)中斷
if not n:
return
print '[CONSUMER] Consuming %s...' % n
time.sleep(2)
message = '200 OK'
def produce(c):
c.next() # 啟動生成器
n = 0
while n < 5:
n = n + 1
print '[PRODUCER] Producing %s...' % n
r = c.send(n) # 通過 send 切換到 consumer 執(zhí)行
print '[PRODUCER] Consumer return: %s' % r
c.close()
if __name__ == '__main__':
c = consumer()
produce(c)
在上面的代碼中,消費者 consumer
是一個生成器函數(shù),我們把它作為參數(shù)傳給 produce
,其中,next 方法用于啟動生成器,send 方法用于發(fā)送消息給 consumer
,并切換到 consumer
執(zhí)行。consumer
通過 yield 獲取到消息,然后進行處理,又通過 yield 返回消息給 produce
,并轉(zhuǎn)到 produce
執(zhí)行,如此反復(fù)。執(zhí)行結(jié)果如下:
[PRODUCER] Producing 1...
[CONSUMER] Consuming 1...
[PRODUCER] Consumer return: 200 OK
[PRODUCER] Producing 2...
[CONSUMER] Consuming 2...
[PRODUCER] Consumer return: 200 OK
[PRODUCER] Producing 3...
[CONSUMER] Consuming 3...
[PRODUCER] Consumer return: 200 OK
[PRODUCER] Producing 4...
[CONSUMER] Consuming 4...
[PRODUCER] Consumer return: 200 OK
[PRODUCER] Producing 5...
[CONSUMER] Consuming 5...
[PRODUCER] Consumer return: 200 OK