小白也能懂的 Python 協程與 asyncio 指南#
一、從生活場景理解異步編程#
1.1 買奶茶的兩種方式#
假設你要買三杯奶茶,每杯製作需要 2 分鐘:
傳統方式(同步):
def 買奶茶_同步():
for _ in range(3):
等待2分鐘() # 幹等著不動
拿奶茶()
# 總耗時:3×2=6分鐘 ❌
聰明方式(異步):
async def 買奶茶_異步():
訂單列表 = [下單(), 下單(), 下單()] # 同時下單
await asyncio.gather(*訂單列表) # 邊等邊玩手機
# 總耗時:2分鐘 ✅
1.2 協程就像外賣小哥#
想像一個外賣小哥同時處理多個訂單:
- 到 A 店取餐(等待時去 B 店)
- 途中接新訂單(靈活調整路線)
- 送達後立即接下一單(不浪費時間)
這就是協程的工作方式!不需要多個小哥(線程),一個就能高效完成任務。
二、最簡協程入門(附可運行代碼)#
2.1 Hello 協程版#
import asyncio
async def 打招呼(name): # 關鍵1:async定義協程
print(f"{name}開始做事")
await asyncio.sleep(1) # 關鍵2:遇到等待就掛起
print(f"{name}事情做完啦")
async def 主任務():
await asyncio.gather(
打招呼("小明"),
打招呼("小紅")
)
asyncio.run(主任務()) # 關鍵3:啟動事件循環
輸出結果:
小明開始做事
小紅開始做事
(等待1秒)
小明事情做完啦
小紅事情做完啦
2.2 執行過程圖解#
三、必須掌握的 3 個核心概念#
3.1 協程三要素#
要素 | 說明 | 類比 |
---|---|---|
async def | 聲明協程函數 | 給外賣訂單貼上 "加急" 標籤 |
await | 暫停並讓出控制權 | 小哥暫時離開去送其他訂單 |
事件循環 | 協調所有任務的調度員 | 外賣平台派單系統 |
3.2 常見誤區清單#
-
錯誤:在普通函數中使用 await
def 普通函數(): await asyncio.sleep(1) # 報錯!
-
錯誤:忘記創建任務
async def 錯誤示例(): # 順序執行,沒有並發! await 任務1() await 任務2()
-
正確做法:
async def 正確示例(): task1 = asyncio.create_task(任務1()) task2 = asyncio.create_task(任務2()) await task1 await task2
四、手把手實戰:下載多張圖片#
4.1 同步版本(龜速)#
import requests
def 下載圖片(url):
print(f"開始下載 {url}")
data = requests.get(url).content
with open("圖片.jpg", "wb") as f:
f.write(data)
print(f"下載完成 {url}")
def 主函數():
urls = ["url1", "url2", "url3"] # 假設3個圖片地址
for url in urls:
下載圖片(url)
# 總耗時:單張耗時 × 數量
4.2 異步版本(飛一般的感覺)#
import aiohttp
async def 異步下載(url):
async with aiohttp.ClientSession() as session:
print(f"開始下載 {url}")
async with session.get(url) as response:
data = await response.read()
with open(f"{url.split('/')[-1]}", "wb") as f:
f.write(data)
print(f"下載完成 {url}")
async def 主任務():
urls = ["url1", "url2", "url3"]
await asyncio.gather(*[異步下載(url) for url in urls])
asyncio.run(主任務())
4.3 性能對比#
圖片數量 | 同步耗時 | 異步耗時 | 速度提升 |
---|---|---|---|
10 | 20s | 2s | 10 倍 |
100 | 200s | 5s | 40 倍 |
五、常見問題解答#
Q1:協程和多線程有什麼區別?#
特性 | 協程 | 多線程 |
---|---|---|
資源占用 | 一個線程搞定所有 | 每個線程需要獨立資源 |
切換方式 | 主動讓出控制權 | 被系統強制切換 |
適用場景 | 適合大量 IO 操作 | 適合計算密集型任務 |
編程難度 | 需要理解異步語法 | 需要處理線程安全問題 |
Q2:什麼時候不能用協程?#
- 需要大量 CPU 計算的場景(如視頻轉碼)
- 使用不支持異步的庫(比如傳統的數據庫驅動)
- 需要跨核並行計算(需結合多進程)
Q3:如何調試協程程序?#
-
使用
asyncio.run()
作為入口 -
在協程內使用普通 print 語句
-
使用專業調試器:
import logging logging.basicConfig(level=logging.DEBUG)
六、最佳學習路線建議#
6.1 新手三步走#
- 先寫同步代碼理解業務流程
- 將耗時操作替換為 async/await
- 用
asyncio.gather
實現並發
6.2 推薦練習項目#
項目類型 | 實現功能 | 技能點 |
---|---|---|
天氣查詢器 | 同時查詢多個城市天氣 | 基礎異步請求 |
網頁監控 | 定時檢查多個網站狀態 | 異步定時任務 |
聊天機器人 | 同時處理多個用戶消息 | 並發消息處理 |
七、記住這 5 句話就夠了#
- async def:聲明協程的起跑線
- await:遇到 IO 就舉手暫停
- 事件循環:幕後總調度員
- create_task:把協程變成可執行任務
- asyncio.run():程序啟動開關
此文由 Mix Space 同步更新至 xLog
原始鏈接為 https://blog.kanes.top/posts/default/asyncio