非同步編程 101:Python async await發展簡史
本文參考了:
- How the heck does async/await work in Python 3.5?
- PEP 380: Syntax for Delegating to a Subgenerator
yield 和 yield from
先讓我們來學習或者回顧一下 yield
和 yieldfrom
的用法。如果你很自信自己完成理解了,可以跳到下一部分。
Python3.3提出了一種新的語法: yieldfrom
。
yield from iterator
本質上也就相當於:
for x in iterator:
yield x
下面的這個例子中,兩個 yieldfrom
加起來,就組合得到了一個大的 iterable
(例子來源於官網3.3 release):
>>> def g(x):
... yield from range(x, 0, -1)
... yield from range(x)
...
>>> list(g(5))
[5, 4, 3, 2, 1, 0, 1, 2, 3, 4]
理解 yieldfrom
對於接下來的部分至關重要。想要完全理解 yieldfrom
,還是來看看官方給的例子:
def accumulate():
tally = 0
while 1:
next = yield
if next is None:
return tally
tally += next
def gather_tallies(tallies):
while 1:
tally = yield from accumulate()
tallies.append(tally)
tallies = []
acc = gather_tallies(tallies)
next(acc) # Ensure the accumulator is ready to accept values
for i in range(4):
acc.send(i)
acc.send(None) # Finish the first tally
for i in range(5):
acc.send(i)
acc.send(None) # Finish the second tally
print(tallies)
我還專門為此錄製了一段視頻,你可以配合文字一起看,或者你也可以打開 pycharm 以及任何調試工具,自己調試一下。 視頻鏈接
來一起 break down:
從 acc=gather_tallies(tallies)
這一行開始,由於 gather_tallies
函數中有一個 yield,所以不會 while1
立即執行(你從視頻中可以看到,acc 是一個 generator 類型)。
next(acc)
:
next()會運行到下一個 yield,或者報StopIteration錯誤。
next(acc)
進入到函數體gathertallies,gathertallies中有一個 yieldfromaccumulate()
,next(acc)不會在這一處停,而是進入到『subgenerator』accumulate裡面,然後在 next=yield
處,遇到了 yield
,然後暫停函數,返回。
for i in range(4):
acc.send(i)
理解一下 acc.send(value)
有什麼用:
- 第一步:回到上一次暫停的地方
- 第二步:把value 的值賦給
xxx=yield
中的xxx
,這個例子中就是next
。
accumulate
函數中的那個while 循環,通過判斷 next
的值是不是 None 來決定要不要退出循環。在 foriinrange(4)
這個for循環裡面,i 都不為 None,所以 while 循環沒有斷。但是,根據我們前面講的:next()會運行到下一個 yield的地方停下來,這個 while 循環一圈,又再次遇到了 yield
,所以他會暫停這個函數,把控制權交還給主線程。
理清一下:對於accumulate來說,他的死循環是沒有結束的,下一次通過 next()恢復他運行時,他還是在運行他的死循環。對於gather_tallies來說,他的
yieldfromaccumulate()
也還沒運行完。對於整個程序來說,確實在主進程和accumulate函數體之間進行了多次跳轉。
接下來看第一個 acc.send(None)
:這時 next
變數的值變成了 None
, ifnextisNone
條件成立,然後返回 tally
給上一層函數。(計算一下,tally 的值為0 + 1 + 2 + 3 = 6)。這個返回值就賦值給了 gather_tallies
中的 gally
。這裡需要注意的是, gather_tallies
的死循環還沒結束,所以此時調用 next(acc)
不會報 StopIteration
錯誤。
for i in range(5):
acc.send(i)
acc.send(None) # Finish the second tally
這一部分和前面的邏輯是一樣的。acc.send(i)會先進入 gather_tallies
,然後進入 accumulate
,把值賦給 next
。 acc.send(None)
停止循環。最後tally的值為10(0 + 1 + 2 + 3 + 4)。
最終tallies列表為: [6,10]
。
Python async await發展簡史
看一下 wikipedia 上 Coroutine的定義:
Coroutines are computer program components that generalize subroutines for non-preemptive multitasking, by allowing execution to be suspended and resumed.
關鍵點在於by allowing execution to be suspended and resumed.(讓執行可以被暫停和被恢復)。通俗點說,就是:
coroutines are functions whose execution you can pause。(來自How the heck does async/await work in Python 3.5?)