1. 前言
Python 以其簡潔的語法與強大的函式庫,深受眾多開發者喜愛。其中的「非同步處理」技術,是實現高效任務處理的重要方法之一。本文將從基礎到進階,清楚說明 Python 中的非同步處理機制。透過學習非同步處理,您將能大幅提升 Web 擷取與 API 請求的處理速度。
2. 非同步處理的基本知識
什麼是非同步處理?
非同步處理是一種程式設計技巧,允許程式在等待一個任務完成的同時,繼續處理其他任務。舉例來說,在擷取多個網頁時,傳統的同步處理會依序發送每一個請求,而非同步處理則可以同時進行多個請求,大幅提高效率。
同步處理與非同步處理的差異
特徵 | 同步處理 | 非同步處理 |
---|---|---|
任務執行順序 | 依序執行一個任務 | 可同時執行多個任務 |
等待時間處理 | 會發生等待時間 | 可在等待時處理其他任務 |
適用範例 | 小型任務處理 | 需要大量 I/O 操作的情境 |
非同步處理的優點
- 提升效率:能同時處理多個任務,善用等待時間。
- 擴展性佳:非常適合處理大量 I/O 操作的應用場景。
- 節省資源:與建立多個執行緒或程序相比,非同步處理更能節省系統資源。
3. 在 Python 中進行非同步處理的基本方法
在 Python 中實現非同步處理的方法
在 Python 中,要進行非同步處理,可以使用 async
與 await
這兩個關鍵字。透過這兩個語法,可以簡潔地撰寫非同步任務。
import asyncio
async def say_hello():
print("こんにちは、非同期処理!")
await asyncio.sleep(1)
print("1秒経過しました!")
asyncio.run(say_hello())
async
:用來定義一個非同步函式。await
:暫停當前的非同步任務,讓其他任務能被執行。
協程(Coroutine)、任務(Task)、事件迴圈(Event Loop)的運作機制
- 協程:非同步任務的基本單位,使用
async
定義的函式就是協程。 - 任務:將協程包裝後交由事件迴圈管理的單位。
- 事件迴圈:Python 執行與排程非同步任務的核心機制。
4. 非同步處理的實際應用範例
在 Python 中,非同步處理可以應用於各種實務場景。以下將透過幾個實際的使用案例進行詳細說明:
- 網頁擷取(Web Scraping)
- API 請求的並行處理
- 資料庫操作的非同步處理
網頁擷取(使用 aiohttp
)
在進行網頁擷取時,通常需要對多個頁面發送請求並收集資料。使用非同步處理後,可以同時發送多個請求,大幅提升速度。
以下為使用 aiohttp
撰寫的非同步網頁擷取範例:
import aiohttp
import asyncio
async def fetch_page(session, url):
async with session.get(url) as response:
print(f"Fetching: {url}")
return await response.text()
async def main():
urls = [
"https://example.com/page1",
"https://example.com/page2",
"https://example.com/page3"
]
async with aiohttp.ClientSession() as session:
tasks = [fetch_page(session, url) for url in urls]
results = await asyncio.gather(*tasks)
print("All pages fetched!")
asyncio.run(main())
- 重點:
- 使用
aiohttp.ClientSession
有效管理多個請求。 - 透過
asyncio.gather
同步執行多個非同步任務。
API 請求的並行處理
在處理多個 API 請求時,非同步處理同樣可以發揮極大的效益。以下為並行發送多個 API 請求並取得結果的範例:
import aiohttp
import asyncio
async def fetch_data(session, endpoint):
async with session.get(endpoint) as response:
print(f"Requesting data from: {endpoint}")
return await response.json()
async def main():
api_endpoints = [
"https://api.example.com/data1",
"https://api.example.com/data2",
"https://api.example.com/data3"
]
async with aiohttp.ClientSession() as session:
tasks = [fetch_data(session, endpoint) for endpoint in api_endpoints]
results = await asyncio.gather(*tasks)
for i, result in enumerate(results):
print(f"Data from endpoint {i + 1}: {result}")
asyncio.run(main())
- 重點:
- 提升多個 API 請求的處理效率。
- 處理回傳的 JSON 格式資料。
資料庫操作的非同步處理(以 aiomysql
為例)
實作非同步的資料庫操作可以讓資料讀寫更為快速。以下為使用 aiomysql
進行非同步查詢的範例:
import aiomysql
import asyncio
async def fetch_from_db():
conn = await aiomysql.connect(
host="localhost",
port=3306,
user="root",
password="password",
db="test_db"
)
async with conn.cursor() as cursor:
await cursor.execute("SELECT * FROM users")
result = await cursor.fetchall()
print("Data from database:", result)
conn.close()
asyncio.run(fetch_from_db())
- 重點:
- 執行非同步查詢以高效取得資料。
- 適用於需同時處理多筆資料庫操作的場景。
5. 使用非同步處理時的注意事項
非同步處理是一種非常強大的工具,但若使用不當,也可能導致一些意想不到的問題。以下將說明在使用非同步處理時需特別注意的要點,以及如何避免這些問題。
避免死結(Deadlock)
死結是一種情況,當多個任務彼此等待資源而無法繼續執行時就會發生。在使用非同步處理時,必須妥善管理任務的順序與資源的取得時機。
範例:發生死結的情境
import asyncio
lock = asyncio.Lock()
async def task1():
async with lock:
print("Task1 acquired the lock")
await asyncio.sleep(1)
print("Task1 released the lock")
async def task2():
async with lock:
print("Task2 acquired the lock")
await asyncio.sleep(1)
print("Task2 released the lock")
async def main():
await asyncio.gather(task1(), task2())
asyncio.run(main())
避免死結的方法
- 事先規劃各任務所需的資源,並以相同順序取得。
- 使用
asyncio.TimeoutError
設定資源取得的超時機制。
防止競爭條件(Race Condition)
在非同步處理中,若多個任務同時存取相同的資源,可能會發生資料不一致的「競爭條件」問題。
範例:競爭條件的情境
import asyncio
counter = 0
async def increment():
global counter
for _ in range(1000):
counter += 1
async def main():
await asyncio.gather(increment(), increment())
print(f"Final counter value: {counter}")
asyncio.run(main())
上述範例中,counter
的值可能無法如預期正確累加。
防止競爭條件的方法
- 使用鎖(Lock):透過
asyncio.Lock
控制同時存取資源的任務。
import asyncio
counter = 0
lock = asyncio.Lock()
async def increment():
global counter
async with lock:
for _ in range(1000):
counter += 1
async def main():
await asyncio.gather(increment(), increment())
print(f"Final counter value: {counter}")
asyncio.run(main())
錯誤處理(Error Handling)的重要性
在非同步處理中,可能會遇到網路錯誤或超時等情況。若未妥善處理這些錯誤,可能會導致整體程式行為異常。
範例:實作錯誤處理
import asyncio
import aiohttp
async def fetch_url(session, url):
try:
async with session.get(url, timeout=5) as response:
return await response.text()
except asyncio.TimeoutError:
print(f"Timeout error while accessing {url}")
except aiohttp.ClientError as e:
print(f"HTTP error: {e}")
async def main():
urls = ["https://example.com", "https://invalid-url"]
async with aiohttp.ClientSession() as session:
tasks = [fetch_url(session, url) for url in urls]
await asyncio.gather(*tasks)
asyncio.run(main())
錯誤處理的重點
- 針對可預期的錯誤進行對應處理。
- 透過例外處理紀錄錯誤日誌,以利後續除錯。
不適合使用非同步處理的情況
非同步處理並非適用於所有情境。特別是在以下幾種情況中,可能不適合使用:
- CPU 密集型任務:
- 例如圖像處理或機器學習訓練等高 CPU 負載的作業,較適合使用
concurrent.futures
或multiprocessing
模組。
- 小型任務:
- 若任務本身耗時極短,初始化非同步處理所帶來的負擔反而超過其效益。
資源管理與最佳化
由於非同步處理會同時執行大量任務,因此有可能瞬間消耗大量記憶體與 CPU。以下是資源管理時的建議:
- 限制同時執行的任務數:
asyncio.Semaphore
可用來控制同時進行的任務數量。
import asyncio
semaphore = asyncio.Semaphore(5)
async def limited_task(task_id):
async with semaphore:
print(f"Running task {task_id}")
await asyncio.sleep(1)
async def main():
tasks = [limited_task(i) for i in range(20)]
await asyncio.gather(*tasks)
asyncio.run(main())
- 監控系統資源:
定期監測執行中的任務數與系統資源使用情況,以防超載。
6. 進階非同步處理的主題
當您掌握了非同步處理的基本概念後,進一步學習其應用與其他技術的比較,能幫助您更有效地活用非同步處理。本節將介紹 Python 以外的非同步技術,以及實際應用範例。
與其他語言的非同步技術比較
不只 Python,許多程式語言也都支援非同步處理。以下將 Python 與幾種熱門技術進行比較,了解各自的特色:
Node.js
Node.js 是一種以非同步處理為強項的 JavaScript 執行環境,特別適合進行高效的 I/O 操作。
特徵 | Python | Node.js |
---|---|---|
主要應用場景 | 資料分析、AI、Web 開發 | Web 伺服器、即時應用 |
非同步處理方式 | asyncio 模組、async /await | Callback、Promise 、async /await |
I/O 處理效能 | 效能不錯,但略低於 Node.js | 針對非同步 I/O 最佳化 |
學習曲線 | 中等偏高 | 相對較低 |
Go
Go(又稱 Golang)使用「Goroutine」這種輕量級執行緒來實現非同步處理。
特徵 | Python | Go |
---|---|---|
主要應用場景 | 通用程式開發 | 伺服器、雲端開發 |
非同步處理方式 | asyncio 模組、async /await | Goroutine、Channel |
平行處理效能 | 效能佳,但不適用於高 CPU 密集作業 | 在平行處理方面表現優異 |
學習曲線 | 中等 | 較低 |
Python 的優勢與應用範圍
- 通用性強:Python 不僅可用於 Web 開發,也廣泛應用於資料分析、機器學習等領域。
- 函式庫豐富:透過
asyncio
、aiohttp
等模組,可簡潔地實現複雜的非同步邏輯。
非同步處理的應用情境
活用非同步處理,可以在以下情境中建構高效率的程式:
伺服器端開發
非同步處理可應用於高負載的伺服器應用中。例如 FastAPI
是一款以非同步 I/O 為核心設計的 Python Web 框架,具有以下優點:
- 高速 API 回應:支援高並行,能有效處理大量請求。
- 簡潔的非同步程式碼:透過
async
/await
編寫簡單明瞭的程式。
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
async def read_root():
return {"message": "Hello, FastAPI!"}
微服務架構
在微服務架構中,應用會由多個小型服務協作構成。非同步處理在此有以下效益:
- 提升服務間通訊效率:使用非同步 HTTP 請求或訊息佇列來降低延遲。
- 增強系統擴展性:可獨立管理每個服務的資源。
即時系統
在聊天應用或線上遊戲等即時系統中,非同步處理可實現流暢的資料更新。例如可使用 websockets
套件進行非同步 WebSocket 通訊。
import asyncio
import websockets
async def echo(websocket, path):
async for message in websocket:
await websocket.send(f"Echo: {message}")
start_server = websockets.serve(echo, "localhost", 8765)
asyncio.get_event_loop().run_until_complete(start_server)
asyncio.get_event_loop().run_forever()
學習非同步處理的進階步驟
若您希望進一步掌握非同步處理,以下主題值得深入學習:
- 進階的非同步設計模式:
- 實作任務取消、超時控制。
- 學習
asyncio
的低階 API(如Future
、自訂事件迴圈)。
- 函式庫的活用:
- 如
aiohttp
、aiomysql
、asyncpg
等,適用於非同步 I/O。 - 非同步 Web 框架(如
FastAPI
、Sanic
)的實務應用。
- 結合分散式處理:
- 搭配分散式架構,可打造具高度擴展性的系統。
7. 總結
本文從基礎到應用,全面說明了 Python 的非同步處理技術。本章節將回顧重點內容,並提供進一步學習的方向。
非同步處理概要
非同步處理可同時有效處理多項任務,特別適用於頻繁 I/O 操作的場景。其主要優勢如下:
- 任務處理效率高:能善用等待時間處理其他任務。
- 具擴展性:能高效處理大量並發請求。
本文涵蓋的重點
- 非同步處理的基本知識
- 介紹同步與非同步的差異。
- 學習
async
和await
的基本用法。
- 實際應用範例
- 以非同步方式執行 Web 擷取與 API 請求。
- 提升資料庫操作的效率。
- 注意事項與潛在問題
- 避免死結與競爭條件的設計方式。
- 妥善處理錯誤與管理資源。
- 進階應用方式
- 與其他非同步技術(Node.js、Go)比較。
- 應用於伺服器開發與即時應用中。
學習非同步處理的下一步
為進一步精通非同步處理,建議深入以下主題:
- 函式庫活用
- 使用
aiohttp
、aiomysql
、asyncpg
進行實作。 - 使用非同步 Web 框架如
FastAPI
或Sanic
開發 Web 應用。
- 進階設計模式
- 學習任務取消、例外處理與非同步佇列的使用。
- 活用
asyncio
的自訂事件迴圈進行低階控制。
- 實作專案應用
- 從小型非同步專案著手實驗。
- 解決實際問題,例如 API 加速或即時通訊等。

8. 常見問答(FAQ)
最後,我們整理了關於 Python 非同步處理的常見問題與解答:
Q1:非同步處理與多執行緒有何不同?
解答:
非同步處理在單一執行緒中透過切換任務來提高效率;而多執行緒則是透過多個執行緒同時處理任務。非同步適用於大量 I/O 任務,多執行緒則適合處理 CPU 密集型的任務。
Q2:有哪些適合學習非同步處理的資源?
解答:
推薦以下資源:
- Python 官方文件中的
asyncio
部分。 - 專門講解非同步處理的書籍(例如《Python Concurrency with Asyncio》)。
- 線上教學平台(如 Real Python 或 YouTube 教學影片)。
Q3:哪些情境適合使用非同步處理?
解答:
以下情況非常適合使用非同步:
- 需要處理大量 Web 請求(如 Web 擷取)。
- 即時通訊應用(如聊天室)。
- 頻繁等待外部 API 或資料庫的任務。
Q4:非同步處理是否適合用於 CPU 密集型任務?
解答:
不適合。CPU 密集型任務應考慮使用 concurrent.futures
或 multiprocessing
模組來達到更好的效能。