Python: 從 async-await 理解協程的高效世界

Python編程的領域中,異步處理就像一位高效的時間管理大師,能讓程序在處理IO密集型任務時大幅提升效率。Python 3.5引入的asyncawait語法糖,讓原本複雜的異步編程變得清晰易懂。接下來,我們就通過日常生活中的例子和簡單代碼,深入理解協程、async/awaitasyncio框架的核心概念。

1. 協程是什麼?

想象你來到圖書館,準備度過充實的一天,想一邊查閱資料,一邊完成練習題。

協程的關鍵特性:

2. 同步 vs 異步

同步執行:

你先去資料室提交打印申請,然後守在打印機旁,直到資料打印完,纔回到座位開始做練習題。整個過程總耗時 = 等資料打印的20分鐘 + 做練習題的30分鐘 = 50分鐘。

異步執行:

你提交打印申請後,不需要一直等着,立刻回到座位開始做練習題。在做題過程中,資料打印好了就去取,取完資料繼續做題。總耗時 = 30分鐘(最長任務時間)。

核心差異:

3. async/await 語法實戰

  1. 定義異步函數(協程)
import asyncio

# 用async聲明異步函數
async def print_material(duration):
    # await 等待異步操作,不阻塞線程
    await asyncio.sleep(duration)
    print(f"資料已打印好,耗時 {duration} 秒")
  1. 執行單個協程
# Python 3.7+ 推薦用法
asyncio.run(print_material(5))  # 輸出:資料已打印好,耗時 5 秒
  1. 併發執行多個任務
async def main():
    # 創建多個任務
    task1 = asyncio.create_task(print_material(3))
    task2 = asyncio.create_task(print_material(5))
    task3 = asyncio.create_task(print_material(2))
    
    # 等待所有任務完成
    await asyncio.gather(task1, task2, task3)

asyncio.run(main())
# 輸出(順序不固定):
# 資料已打印好,耗時 2 秒
# 資料已打印好,耗時 3 秒
# 資料已打印好,耗時 5 秒

重要提示:

await只能用於可等待對象(如asyncio.sleep()),直接使用time.sleep()會阻塞線程asyncio.create_task()用於註冊併發任務,asyncio.gather()可批量等待任務完成

4. 協程 vs 線程

爲何協程更適合IO場景?

3dh34E

實際案例:

5. asyncio 框架

asyncioPython內置的異步框架,它就像一個智能管家,負責管理協程的執行流程,其內部實現異步的原理主要包含以下幾個關鍵部分:

1. 事件循環(Event Loop)

事件循環是 asyncio 的核心,它就像是一個不停運轉的 “調度員”。這個調度員手中有一張任務清單,不斷地檢查清單裏的任務是否有可以執行的。當某個協程遇到await時,就會暫停執行,並把執行權交回事件循環。事件循環會記錄下這個協程暫停的位置,然後去檢查其他任務。一旦await後面的異步操作完成(比如asyncio.sleep()時間結束),事件循環就會把這個協程重新放到任務清單中,等待合適的時機繼續執行。

比如,在前面圖書館的例子中,你在等待資料打印時開始做題,這個 “切換任務” 的動作就像是事件循環在調度。當資料打印好這個事件發生,事件循環就會提醒你去取資料。

import asyncio

asyncdef task1():
    print("任務1開始執行")
    await asyncio.sleep(2)
    print("任務1執行完畢")

asyncdef task2():
    print("任務2開始執行")
    await asyncio.sleep(1)
    print("任務2執行完畢")

asyncdef main():
    loop = asyncio.get_event_loop()
    task_list = [loop.create_task(task1()), loop.create_task(task2())]
    await asyncio.gather(*task_list)

asyncio.run(main())

在這段代碼中,事件循環loop負責調度task1task2,根據await asyncio.sleep()的時間,靈活安排任務的執行順序。

2. 任務(Task)和 Future

asyncio.Task是對協程的進一步封裝,它代表一個具體的異步任務。當我們使用asyncio.create_task()創建任務時,實際上是將協程包裝成了一個Task對象,並加入到事件循環的任務隊列中。Task對象有自己的狀態(如 pendingrunningdone 等),事件循環通過這些狀態來管理任務的執行。

Future則用於表示異步操作的最終結果。當一個協程完成時,會將結果存儲在對應的Future對象中。比如,當一個網絡請求的協程獲取到數據後,數據就會存放在相關的Future裏,其他協程可以通過await這個Future來獲取結果。

3. 回調機制

asyncio支持爲任務添加回調函數。當一個異步任務完成時,事件循環會自動調用預先設置的回調函數。這就好比你點外賣時,設置了外賣送達時的提醒,外賣送到後手機就會觸發提醒(回調函數執行)。

import asyncio

def callback(future):
    print(f"任務結果:{future.result()}")

asyncdef async_task():
    await asyncio.sleep(3)
    return"任務執行成功"

asyncdef main():
    task = asyncio.create_task(async_task())
    task.add_done_callback(callback)
    await task

asyncio.run(main())

在上述代碼中,當async_task執行完畢,callback函數就會被調用,輸出任務的結果。

4. 協程的掛起與恢復

await關鍵字是實現協程掛起和恢復的關鍵。當協程執行到await時,會暫停當前協程的執行,並將控制權交回事件循環。此時,事件循環可以去執行其他可運行的協程。當await後面的操作完成,事件循環會恢復該協程的執行,從暫停的位置繼續運行。這種機制使得在單線程內可以高效地處理多個異步任務,避免了因等待IO而造成的線程阻塞。

核心概念:

6. 總結:何時選擇異步協程?

推薦使用場景:

不適合場景:

掌握 async/await 異步編程,能讓你的程序在處理IO密集型任務時效率倍增。無論是開發高併發的網絡應用,還是優化日常腳本,協程都能成爲你的得力助手。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/PlQFlf_pwISAm3ZlDOB18A