Python可以使用線程運行多個函數,使得這些函數看上去好像是在同一時間得到執行。然而,線程有三個顯著的缺點:
- 爲了確保數據安全,我們必須使用特殊的工具來協調這些線程。便使得線程代碼變得難於擴展和維護;
- 線程需要佔用大量內存,每個正在執行的線程,大約佔據
8MB
的內存,如果在程序中運行上萬的函數線程時,會導致計算機內存無法承受; - 線程啓動時的開銷比較大。如果程序不停地以靠新線程來同時執行多個函數,並等待這些線程結束,那麼使用線程所引發的開銷,就會拖慢整個程序的執行速度。
協程定義
Python的協程可以解決上述問題,它使得Python程序看上去好像是同時運行多個函數。協程的實現方式,實際上是對生成器(generator
)的一種擴展。同時啓動協程所需的開銷,與調用函數的開銷相仿。
協程的工作原理
原理
:每當生成器函數執行到yield
表達式的時候,消耗生成器的那段代碼,就通過send
方法給生成器回傳一個值。而生成器在收到經由send
函數所傳進來的這個值後,會將其視爲yield
表達式的執行結果。
def my_coroutine():
while True:
received = yield
print("Received:",received)
it = my_coroutine()
next(it) ###調用一次next函數,使得運行到yield語句處
it.send('First')
it.send('Second')
輸出結果:
Received:First
Received:Second
案例
:編寫一個生成器協程,一次發送多個數值,給出當前統計到的最小值。
def minimize():
current = yield
while True:
value = yield current
current = min(current,value)
it = minimize()
next(it)
print(it.send(10)) #10賦給了current
print(it.send(4))
print(it.send(22))
print(it.send(-4))
生成器函數似乎一直運行,每次調用send
之後,都會產生新值賦給value
。協程會在生成器函數中每個yield
表達式那裏暫停,等到外界再次調用send
方法之後,將send
的值返回給value
,同時繼續執行到下一個yield
表達式。
注意:yield
語句的使用,x = yield y
表明yield
語句返回數值y
,接收數值x
,即由send
方法傳進來的x
,而y
則作爲返回值返回給調用者。
協程的使用
最後,來看一個生成者-消費者模型。傳統的生產者-消費者模型,是一個線程寫消息,一個線程讀消息,通過鎖機制控制隊列和等待,但是容易導致死鎖。
協程實現的生產者-消費者模型。
def consumer():
r = ''
while True:
n = yield r
if not n:
return
print('[CONSUMER] Consuming %s...' % n)
r = '200 OK'
def produce(c):
c.send(None)
n = 0
while n < 5:
n = n + 1
print('[PRODUCER] Producing %s...' % n)
r = c.send(n)
print('[PRODUCER] Consumer return: %s' % r)
c.close()
c = consumer()
produce(c)
輸出結果:
[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
可以看出,生產者produce
生產消息後,直接通過send
方法跳轉到消費者consumer
開始執行,待消費者consumer
執行完畢到下一個yield
處,切換回生產者produce
繼續生產,效率極高。
執行流程:
- 首先調用
c.send(None)
啓動生成器; - 然後,一旦生產了東西,通過
c.send(n)
切換到consumer
執行; consumer
通過yield
拿到消息,處理,又通過yield
把結果傳回;produce
拿到consumer
處理的結果,繼續生產下一條消息;produce
決定不生產了,通過c.close()
關閉consumer
,整個過程結束。
整個流程無鎖,由一個線程執行,produce
和consumer
協作完成任務,所以稱爲“協程”,而非線程的搶佔式多任務。