協程的發展流程
再來回顧一下協程的發展流程:
python2.5 爲生成器引用.send()、.throw()、.close()方法
python3.3 爲引入yield from,可以接收返回值,可以使用yield from定義協程
Python3.4 加入了asyncio模塊
Python3.5 增加async、await關鍵字,在語法層面的提供支持
python3.7 使用 async def + await 的方式定義協程
python3.10 移除 以 yield from 的方式定義協程
舊協程
是指以yield
、yield from
等生成器語法爲基礎的協程實現
新協程
是指以asyncio
、async
、await
等關鍵字爲基礎的協程實現
兩種協程的實現方式在協程發展史上有一段交集,並且舊協程基於生成器的協程語法讓生成器和協程兩個概念混淆,所以對學習者會造成一定的困擾。本篇主要說明兩種協程的實現方式的差異。
舊協程回顧
舊協程以yield關鍵字爲核心,通過yield關鍵提供的代碼執行暫停、恢復的能力,實現函數交替的執行,cpu的轉讓等能力。
import time
def consume():
r = ''
while True:
n = yield r
print(f'[consumer] 開始消費 {n}...')
time.sleep(1)
r = f'{n} 消費完成'
def produce(c):
next(c)
n = 0
while n < 5:
n = n + 1
print(f'[producer] 生產了 {n}...')
r = c.send(n)
print(f'[producer] consumer return: {r}')
c.close()
if __name__=='__main__':
c = consume()
produce(c)
執行結果:
[producer] 生產了 1...
[consumer] 開始消費 1...
[producer] consumer return: 1 消費完成
[producer] 生產了 2...
[consumer] 開始消費 2...
[producer] consumer return: 2 消費完成
[producer] 生產了 3...
[consumer] 開始消費 3...
[producer] consumer return: 3 消費完成
[producer] 生產了 4...
[consumer] 開始消費 4...
[producer] consumer return: 4 消費完成
[producer] 生產了 5...
[consumer] 開始消費 5...
[producer] consumer return: 5 消費完成
結果分析:
當消費者consume
執行到n = yield r
時,流程暫停,將cpu交還給調用方produce
。
在asyncio初識篇中提到過,協程最重要的兩個因素是事件循環
+ 任務
。用yield實現的協程中,consume
和 produce
中的 while循環共同作用下實現了一個事件循環的功能,yield
和 send
實現了任務的暫停和繼續執行。
總結來說協程需要的兩個能力事件循環
和任務暫停和繼續
,在舊協程中的實現分別是:
- 事件循環通過手動編寫while循環代碼實現
- 代碼暫停繼續執行通過yield生成器的能力實現
新協程回顧
新協程是asyncio
、async
、await
等關鍵字實現的。新協程是基於事件循環機制實現的,核心能力包括事件循環,任務,回調機制等。三者提供的能力分別是
- asyncio 提供了事件循環
- async 提供了協程標識
- await 提供了流程掛起能力
import asyncio
async def coro1():
print("start coro1")
await asyncio.sleep(2)
print("end coro1")
async def coro2():
print("start coro2")
await asyncio.sleep(1)
print("end coro2")
# 創建事件循環
loop = asyncio.get_event_loop()
# 創建任務
task1 = loop.create_task(coro1())
task2 = loop.create_task(coro2())
# 運行協程
loop.run_until_complete(asyncio.gather(task1, task2))
# 關閉事件循環
loop.close()
結果
start coro1
start coro2
end coro2
end coro1
結果分析:
當coro1
執行到 await asyncio.sleep(2)
時,流程掛起,將cpu交還給事件循環,等待事件循環的下一次調度,而事件循環調度到coro2
繼續執行。
協程的兩個重要能力事件循環
和 任務暫停和繼續
,分別的實現者:
- 事件循環通過asyncio提供的loop實現
- 程序掛起通過 await 關鍵字實現
新舊協程實現的對比
asyncio
和 yield
是用於實現異步編程的兩種不同的機制。
yield
是一種用於生成器(Generator)函數的關鍵字,用於創建可暫停和恢復執行的函數。當一個函數中包含 yield
語句時,它會返回一個生成器對象,可以通過調用生成器的 next()
方法或使用 for
循環來逐步迭代生成器函數中的值。
通過使用 yield
,我們可以將一個函數分割成多個代碼塊,並在每個代碼塊之間進行切換執行。這使得我們可以在函數執行過程中臨時掛起函數的執行,然後再次恢復執行。
asyncio
是 Python 提供的標準庫,用於編寫異步代碼。它基於事件循環(Event Loop)模式,允許我們在單線程中處理多個併發任務,並通過協程(Coroutine)來管理異步操作。
asyncio
使用了 async
和 await
這兩個關鍵字來定義協程函數。在協程函數中可以使用 await
關鍵字來暫停當前協程的執行,等待某個異步操作的完成,然後恢復執行。
總結來說:
舊協程:通過yield關鍵字的暫停和恢復執行的能力實現協程
新協程:通過事件循環機制,await關鍵字掛起流程能力實現協程
await 和 yield 的關係
await
關鍵字和 yield
關鍵字都可以用於控制流的暫停和恢復,都屬於python的關鍵字,但是它們在協程的實現上有所不同。
相同點:
- 控制流暫停和恢復:無論是
await
還是yield
,它們都可以使代碼在某個點暫停執行,並在稍後的時間點繼續執行。 - 協程支持:
await
和yield
都與協程(Coroutine)密切相關。它們都能夠用於定義和管理協程,使得異步代碼的編寫更加簡單和易讀。
區別:
- 語法差異:
await
是 Python 3.5 引入的關鍵字,用於異步函數中暫停執行等待異步操作完成。而yield
是早期協程的關鍵字,主要用於生成器(Generator)函數,用於創建迭代器和實現惰性計算,早期通過生成器的能力來實現協程。 - 語義:
-
await
表示當前協程需要等待一個異步操作的完成,並掛起執行,讓其他任務有機會執行。 -
yield
是將執行的控制權交給調用方,同時保存函數的狀態,以便在下次迭代時從上一次暫停的位置恢復執行。await將程序掛起,讓事件循環調度新的任務。yield將程序掛起,等待調用方的下一步指令。
- 上下文:
await
必須在異步上下文中使用,例如在異步函數中或者在async with
塊中。而yield
可以在普通函數中使用,即使沒有使用協程的上下文。 - 返回值:
yield
返回生成器對象,通過調用next()
方法或使用for
循環逐步迭代生成器中的值。而await
返回一個可等待對象(Awaitable),它可以是Future
、Task
、Coroutine
等。
總結:
await 不是通過 yield 來實現的程序暫停和執行,兩者有相似的能力,但完全沒有調用關係,都是屬於python關鍵字。
await
適用於異步編程場景,用於等待異步操作的完成,同時支持更靈活的協程管理。yield
則主要用於生成器函數,用於實現迭代器和惰性計算。
它們在應用場景和語法上存在一些差異,但都爲我們提供了控制流的暫停和恢復的能力。
以上就是新舊協程的實現方法,對比了兩種協程的實現方法,比較了yield關鍵字既作爲生成器又實現協程有點混淆的用法,比較了都可以暫停恢復的關鍵字yield和await。這些內容是協程原理的核心知識,理解有難度。