Python3 協程相關

什麼是協程

  協程(Coroutine),又稱微線程,纖程。一般咱們認爲線程是輕量級的進程,所以咱們也把協程理解爲輕量級的線程即微線程。python

  協程的做用是在執行函數A時能夠隨時中斷去執行函數B,而後中斷函數B繼續執行函數A(能夠自由切換)。這裏的中斷,不是函數的調用,而是有點相似CPU的中斷。這一整個過程看似像多線程,然而協程只有一個線程執行。多線程

協程的優點

  • 執行效率極高,由於是子程序(函數)切換不是線程切換,由程序自身控制,沒有切換線程的開銷。因此與多線程相比,線程的數量越多,協程的性能優點越明顯。
  • 不須要機制,由於只有一個線程,也不存在同時寫變量衝突,在控制共享資源時也不須要加鎖,只須要判斷狀態,所以執行效率高的多。
  • 協程能夠處理IO密集型程序的效率問題,但不適合處理CPU密集型程序,如要充分發揮CPU利用率應結合多進程+協程。

Python3中的協程

生成器 yield/send

yield + send(利用生成器實現協程)

例:併發

def simple_coroutine():
    print('-> start')
    x = yield
    print(x)
    print('-> end')

#主線程
sc = simple_coroutine()
# 可使用sc.send(None),效果同樣
next(sc) #預激
sc.send('go')

執行結果以下:異步

-> start
go
-> end

拋出StopIteration
  • simple_coroutine()是一個生成器,由next(sc) 預激,啓動協程,執行到第一個yield中斷
  • send()方法給yield傳入參數,繼續執行async

    協程的四個狀態

  • GEN_CREATED:等待開始執行
  • GEN_RUNNING:解釋器正在執行
  • GEN_SUSPENED:在yield表達式處暫停
  • GEN_CLOSED:執行結束函數

    協程終止

  • 協程中未處理的異常會向上冒泡,傳給 next 函數或 send 方法的調用方(即觸發協程的對象)
  • 終止協程的一種方式:發送某個哨符值,讓協程退出。內置的 None 和Ellipsis 等常量常常用做哨符值oop

@asyncio.coroutine和yield from

asyncio.coroutione

  asyncio是Python3.4版本引入的標準庫,直接內置了對異步IO的支持。asyncio的異步操做,須要在coroutine中經過yield from完成。
例:性能

import asyncio
@asyncio.coroutine
def test(i):
    print('test_1', i)
    r = yield from asyncio.sleep(1)
    print('test_2', i)

loop = asyncio.get_event_loop()
tasks = [test(i) for i in range(1,3)]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()
  • @asyncio.coroutine把一個generator標記爲coroutine類型,而後就把這個coroutine扔到EventLoop中執行。test()會首先打印出test_1
  • 而後yield from語法可讓咱們方便地調用另外一個generator。因爲asyncio.sleep()也是一個coroutine,因此線程不會等待asyncio.sleep(),而是直接中斷並執行下一個消息循環。
  • asyncio.sleep()返回時,線程就能夠從yield from拿到返回值(此處是None)
  • 而後接着執行下一行語句。把asyncio.sleep(1)當作是一個耗時1秒的IO操做,在此期間主線程並未等待,而是去執行EventLoop中其餘能夠執行的coroutine了,所以能夠實現併發執行

結果以下:線程

test_1 1
test_1 2
test_2 1
test_2 2

yield from

  當yield from 後面加上一個生成器後,就實現了生成的嵌套。
  實現生成器的嵌套,並非必定必需要使用yield from,而是使用yield from可讓咱們避免讓咱們本身處理各類料想不到的異常,而讓咱們專一於業務代碼的實現。
  若是本身用yield去實現,那隻會加大代碼的編寫難度,下降開發效率,下降代碼的可讀性。code

  • 調用方:調用委派生成器的客戶端(調用方)代碼
  • 委託生成器:包含yield from表達式的生成器函數
  • 子生成器:yield from後面加的生成器函數
# 子生成器
def average_gen():
    total = 0
    count = 0
    average = 0
    while True:
        new_num = yield average
        count += 1
        total += new_num
        average = total/count

# 委託生成器
def proxy_gen():
    while True:
        yield from average_gen()

# 調用方
def main():
    calc_average = proxy_gen()
    next(calc_average)# 預激下生成器
    print(calc_average.send(10))  
    print(calc_average.send(20))  
    print(calc_average.send(30))  

main()

結果以下:

10.0
15.0
20.0

委託生成器的做用:
在調用方與子生成器之間創建一個雙向通道。

雙向通道就是調用方能夠經過send()直接發送消息給子生成器,而子生成器yield的值,也是直接返回給調用方。

爲何要用yield from

  yield from幫咱們作了不少的異常處理,而這些若是咱們要本身去實現的話,一個是編寫代碼難度增長,寫出來的代碼可讀性不好,極可能有遺漏,只要哪一個異常沒考慮到,都有可能致使程序崩潰。

async/await關鍵字

  爲了簡化並更好地標識異步IO,從Python 3.5開始引入了新的語法async和await,可讓coroutine的代碼更簡潔易讀。

可將上面的案例代碼改造以下:

import asyncio
# @asyncio.coroutine
async def test(i):
    print('test_1', i)
    # r = yield from asyncio.sleep(1)
    await asyncio.sleep(1)
    print('test_2', i)

loop = asyncio.get_event_loop()
tasks = [test(i) for i in range(1,3)]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()

執行結果

test_1 1
test_1 2
test_2 1
test_2 2
  • async 替換 @asyncio.coroutine,加在def以前用於修飾
  • await替換yield from
相關文章
相關標籤/搜索