第40條 考慮協程來併發運行多個函數

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,整個過程結束。

整個流程無鎖,由一個線程執行,produceconsumer協作完成任務,所以稱爲“協程”,而非線程的搶佔式多任務。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章