與子程序(或者說函數(shù))一樣,協(xié)程(coroutine)也是一種程序組件。Donald Knuth 曾說,子程序是協(xié)程的特例。
一個(gè)子程序就是一次函數(shù)調(diào)用,它只有一個(gè)入口,一次返回,調(diào)用順序是明確的。但協(xié)程的調(diào)用和子程序則大不一樣,協(xié)程允許有多個(gè)入口對(duì)程序進(jìn)行中斷、繼續(xù)執(zhí)行等操作。
Python2 可以通過 yield 來實(shí)現(xiàn)基本的協(xié)程,但不夠強(qiáng)大,第三方庫(kù) gevent 對(duì)協(xié)程提供了強(qiáng)大的支持。另外,Python3.5 提供了 async/await 語(yǔ)法來實(shí)現(xiàn)對(duì)協(xié)程的支持。本文只討論通過 yield 來實(shí)現(xiàn)協(xié)程。
對(duì)于經(jīng)典的生產(chǎn)者-消費(fèi)者模型,如果用多線程來實(shí)現(xiàn),我們就需要一個(gè)線程寫消息,一個(gè)線程讀消息,而且需要鎖機(jī)制來避免對(duì)共享資源的訪問沖突。
相比多線程,協(xié)程的一大特點(diǎn)就是它在一個(gè)線程內(nèi)執(zhí)行,既避免了多線程之間切換帶來的開銷,也避免了對(duì)共享資源的訪問沖突。
下面,讓我們看看怎么用 yield 來實(shí)現(xiàn)簡(jiǎn)單的生產(chǎn)者-消費(fèi)者模型。
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() # 啟動(dòng)生成器
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)
在上面的代碼中,消費(fèi)者 consumer 是一個(gè)生成器函數(shù),我們把它作為參數(shù)傳給 produce,其中,next 方法用于啟動(dòng)生成器,send 方法用于發(fā)送消息給 consumer,并切換到 consumer 執(zhí)行。consumer 通過 yield 獲取到消息,然后進(jìn)行處理,又通過 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