Python: 從 async-await 理解協程的高效世界
在Python
編程的領域中,異步處理就像一位高效的時間管理大師,能讓程序在處理IO
密集型任務時大幅提升效率。Python 3.5
引入的async
和await
語法糖,讓原本複雜的異步編程變得清晰易懂。接下來,我們就通過日常生活中的例子和簡單代碼,深入理解協程、async
/await
及asyncio
框架的核心概念。
1. 協程是什麼?
想象你來到圖書館,準備度過充實的一天,想一邊查閱資料,一邊完成練習題。
-
進程:就好比整個圖書館,是程序運行的環境,裏面有各種資源和空間供任務開展。
-
線程:像是圖書館裏不同的自習室,每個自習室可以容納不同的人做不同的事,它們相對獨立又共享圖書館的資源。
-
協程:則是你本人,在等資料打印出來(
IO
等待)的時間裏,不會幹坐着,而是立刻開始做練習題;當練習題遇到不會的,又切換去看看資料是否打印好。哪件事先有結果(資料打印好了或者做出了一道題)就先處理哪件。這就是協程的核心 —— 在單線程內靈活切換任務,避免因等待IO
操作而浪費時間。
協程的關鍵特性:
-
在單線程內執行,任務切換成本極低
-
特別適合處理網絡請求、文件讀寫這類
IO
密集型任務 -
無法利用多核
CPU
,處理CPU
密集型任務需要搭配多進程
2. 同步 vs 異步
同步執行:
你先去資料室提交打印申請,然後守在打印機旁,直到資料打印完,纔回到座位開始做練習題。整個過程總耗時 = 等資料打印的20
分鐘 + 做練習題的30
分鐘 = 50
分鐘。
異步執行:
你提交打印申請後,不需要一直等着,立刻回到座位開始做練習題。在做題過程中,資料打印好了就去取,取完資料繼續做題。總耗時 = 30
分鐘(最長任務時間)。
核心差異:
-
同步:任務按順序依次執行,必須等前一個任務完成才能進行下一個
-
異步:任務啓動後不阻塞後續操作,通過回調或事件通知任務完成
3. async/await 語法實戰
- 定義異步函數(協程)
import asyncio
# 用async聲明異步函數
async def print_material(duration):
# await 等待異步操作,不阻塞線程
await asyncio.sleep(duration)
print(f"資料已打印好,耗時 {duration} 秒")
- 執行單個協程
# Python 3.7+ 推薦用法
asyncio.run(print_material(5)) # 輸出:資料已打印好,耗時 5 秒
- 併發執行多個任務
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
場景?
實際案例:
-
下載 1000 張圖片: 協程方案能在單線程內高效完成,線程方案創建大量線程容易導致資源耗盡
-
數據加密計算: 線程配合多進程可以利用多核
CPU
,協程無法提升計算速度
5. asyncio 框架
asyncio
是Python
內置的異步框架,它就像一個智能管家,負責管理協程的執行流程,其內部實現異步的原理主要包含以下幾個關鍵部分:
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
負責調度task1
和task2
,根據await asyncio.sleep()
的時間,靈活安排任務的執行順序。
2. 任務(Task)和 Future
asyncio.Task
是對協程的進一步封裝,它代表一個具體的異步任務。當我們使用asyncio.create_task()
創建任務時,實際上是將協程包裝成了一個Task
對象,並加入到事件循環的任務隊列中。Task
對象有自己的狀態(如 pending
、running
、done
等),事件循環通過這些狀態來管理任務的執行。
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
而造成的線程阻塞。
核心概念:
-
事件循環(Event Loop):異步任務的調度中心,不斷檢查任務狀態
-
任務(Task):對協程的封裝,用於管理執行狀態
-
Future:表示異步操作的最終結果,可通過 await 獲取
6. 總結:何時選擇異步協程?
推薦使用場景:
-
大量併發的網絡請求(如網頁爬蟲、API 調用)
-
異步
IO
操作(數據庫連接、文件讀寫) -
實時通信(
WebSocket
、聊天應用)
不適合場景:
-
純
CPU
計算任務(如數據壓縮、圖像渲染) -
對執行順序要求嚴格的任務(如金融交易流程)
掌握 async/await
異步編程,能讓你的程序在處理IO
密集型任務時效率倍增。無論是開發高併發的網絡應用,還是優化日常腳本,協程都能成爲你的得力助手。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/PlQFlf_pwISAm3ZlDOB18A