Python協程
目錄
由於Golang的goroutine太過簡單易用,我再次嘗試了一遍Python的asyncio。
1. 簡介
協程,英文叫作 Coroutine,又稱微執行緒、纖程,協程是一種使用者態的輕量級執行緒。
協程擁有自己的暫存器上下文和棧。協程排程切換時,將暫存器上下文和棧儲存到其他地方,在切回來的時候,恢復先前儲存的暫存器上下文和棧。因此協程能保留上一次呼叫時的狀態,即所有區域性狀態的一個特定組合,每次過程重入時,就相當於進入上一次呼叫的狀態。
協程本質上是個單程序,協程相對於多程序來說,無需執行緒上下文切換的開銷,無需原子操作鎖定及同步的開銷,程式設計模型也非常簡單。
我們可以使用協程來實現非同步操作,比如在網路爬蟲場景下,我們發出一個請求之後,需要等待一定的時間才能得到響應,但其實在這個等待過程中,程式可以幹許多其他的事情,等到響應得到之後才切換回來繼續處理,這樣可以充分利用 CPU 和其他資源,這就是協程的優勢。
1.1. 協程相對於多執行緒的優點
多執行緒程式設計是比較困難的,因為排程程式任何時候都能中斷執行緒, 必須記住保留鎖,去保護程式中重要部分, 防止多執行緒在執行的過程中斷。
而協程預設會做好全方位保護, 以防止中斷。我們必須顯示產出才能讓程式的餘下部分執行。對協程來說, 無需保留鎖, 而在多個執行緒之間同步操作, 協程自身就會同步, 因為在任意時刻, 只有一個協程執行。總結下大概下面幾點:
- 無需系統核心的上下文切換,減小開銷;
- 無需原子操作鎖定及同步的開銷,不用擔心資源共享的問題;
- 單執行緒即可實現高併發,單核 CPU 即便支援上萬的協程都不是問題,所以很適合用於高併發處理,尤其是在應用在網路爬蟲中。
2. 操作
相比於Golang中的goroutine,Python的協程還需要理解下面幾個概念。
- event_loop:事件迴圈,相當於一個無限迴圈,我們可以把一些函式註冊到這個事件迴圈上,當滿足條件發生的時候,就會呼叫對應的處理方法。
- coroutine:中文翻譯叫協程,在 Python 中常指代為協程物件型別,我們可以將協程物件註冊到事件迴圈中,它會被事件迴圈呼叫。我們可以使用
async
- task:任務,它是對協程物件的進一步封裝,包含了任務的各個狀態。
- future:代表將來執行或沒有執行的任務的結果,實際上和 task 沒有本質區別。
另外我們還需要了解 async/await
關鍵字,它是從 Python 3.5
才出現的,專門用於定義協程。其中,async
定義一個協程,await
## 用來掛起阻塞方法的執行。
2.1. 定義非同步函式
- 使用
async def function(params)
- 使用
await
定義IO操作(Python通過該關鍵字在耗時的IO運算上切換協程,並自動監聽)
async def say_after(delay, what):
await asyncio.sleep(delay)
print(what)
async def main():
print(f"started at {time.strftime('%X')}")
await say_after(1, 'hello')
await say_after(2, 'world')
print(f"finished at {time.strftime('%X')}")
2.2. 任務 & 事件迴圈
- 建立任務
asyncio.create_task()
# Python 3.7asyncio.ensure_future()
# Python 3.7 之前
- 建立事件迴圈
loop = asyncio.get_event_loop()
task = loop.create_task(coroutine)
loop.run_until_complete(future)
loop.run_forever()
執行事件迴圈直到 stop() 被呼叫。loop.stop()
async def coro():
...
async def main():
# In Python 3.7+
task = asyncio.create_task(coro())
...
# This works in all Python versions but is less readable
task = asyncio.ensure_future(coro())
await task # 簡單等待(掛起當前協程並切換IO)直到完成,程式繼續
...
asyncio.run(main())
2.3. Task回撥
add_done_callback(callback, *, context=None)
新增一個回撥,將在 Task 物件完成時被執行。
2.4. 執行 asyncio 程式
async def main():
await asyncio.sleep(1)
print('hello')
asyncio.run(main())
2.5. 簡單等待
coroutine asyncio.wait(aws, *, loop=None, timeout=None, return_when=ALL_COMPLETED)
併發執行 aws 指定的 可等待物件 並阻塞執行緒直到滿足 return_when 指定的條件。返回 Task/Future 集合: (done, pending)
。
注意:【Python3.8】直接向 wait() 傳入協程物件的方式已棄用。
最新推薦用法:
async def foo():
return 42
task = asyncio.create_task(foo())
done, pending = await asyncio.wait({task})
if task in done:
# Everything will work as expected now.