Python 非同步 vs 執行緒:何時使用 asyncio

Backend2026-09-11TryQuickToolBox

為什麼並行在 Python 中很重要

你有一個從多個 API 獲取資料的 Python 腳本。它能運作,但很慢——每個請求都要等待前一個完成。你聽過 asyncio 和執行緒,但該用哪一個?選擇錯誤的模型可能導致程式碼不必要地複雜或極度緩慢。

本指南將釐清困惑。我們會比較 Python 的非同步和執行緒模型,確切說明何時該使用哪一種,並提供你可以套用到自己專案的實用範例。

了解 Python 的並行環境

Python 提供三種主要的並行模型:

GIL 是一個互斥鎖,防止多個執行緒同時執行 Python 位元組碼。這表示執行緒無法加速 CPU 密集任務,但確實有助於 I/O 密集任務,因為在 I/O 操作期間 GIL 會被釋放。

何時使用 asyncio

當你的程式大部分時間都在等待外部事件(網路請求、檔案 I/O、資料庫查詢或計時器)時,asyncio 表現出色。因為它使用單一執行緒,你可以避免執行緒建立和上下文切換的開銷。事件迴圈能有效管理數千個並行連線。

在以下情況使用 asyncio:

以下是一個簡單範例,同時獲取多個 URL:

import asyncio
import aiohttp

async def fetch(url):
    async with aiohttp.ClientSession() as session:
        async with session.get(url) as response:
            return await response.text()

async def main():
    urls = [
        'https://example.com',
        'https://example.org',
        'https://example.net',
    ]
    tasks = [fetch(url) for url in urls]
    results = await asyncio.gather(*tasks)
    print(f'Fetched {len(results)} pages')

asyncio.run(main())

何時使用執行緒

當你有阻塞式 I/O 操作且沒有非同步對應版本時,或者當你使用不支援非同步的舊程式碼時,執行緒是個好選擇。對於少量任務,它們也更容易理解。

在以下情況使用執行緒:

使用 concurrent.futures.ThreadPoolExecutor 的範例:

from concurrent.futures import ThreadPoolExecutor
import requests

def fetch(url):
    response = requests.get(url)
    return response.text

urls = [
    'https://example.com',
    'https://example.org',
    'https://example.net',
]

with ThreadPoolExecutor(max_workers=10) as executor:
    results = list(executor.map(fetch, urls))

print(f'Fetched {len(results)} pages')

比較 asyncio 和執行緒

以下是並排比較,幫助你決定:

方面 asyncio 執行緒
並行模型 單執行緒事件迴圈 多個 OS 執行緒
最適合 I/O 密集、高並行 I/O 密集、阻塞呼叫
CPU 密集效能 差 (GIL) 差 (GIL)
可擴展性 數千個任務 數百個執行緒
複雜度 需要 async/await 語法 熟悉的同步風格
除錯 堆疊追蹤可能棘手 標準除錯工具
生態系統 非同步支援持續成長 通用

那 CPU 密集任務呢?

由於 GIL,asyncio 和執行緒都無法幫助你在 Python 中平行處理 CPU 密集工作。對於 CPU 密集任務——例如數值計算、影像處理或資料壓縮——請使用 multiprocessing 或 concurrent.futures.ProcessPoolExecutor。

範例:

from concurrent.futures import ProcessPoolExecutor

def cpu_heavy(n):
    return sum(i * i for i in range(n))

if __name__ == '__main__':
    with ProcessPoolExecutor() as executor:
        results = list(executor.map(cpu_heavy, [10**6, 10**6, 10**6]))
    print(results)

最佳實踐和陷阱

常見問題

我可以同時使用 asyncio 和執行緒嗎?

可以。你可以使用 asyncio.to_thread() 或 loop.run_in_executor() 在執行緒池中執行阻塞程式碼。當你需要從非同步程式碼呼叫同步函式庫而不阻塞事件迴圈時,這很有用。

asyncio 能與 GIL 一起運作嗎?

是的,asyncio 在單一執行緒中運行,受 GIL 約束。然而,因為它是為 I/O 密集任務設計的,在 I/O 操作期間 GIL 會被釋放,允許其他任務運行。對於 CPU 密集工作,asyncio 不會提供加速。

哪個更快:asyncio 還是執行緒?

對於有許多並行操作的 I/O 密集任務,asyncio 通常更快且更具可擴展性,因為它避免了執行緒開銷。對於少量阻塞任務,執行緒可能更簡單且效能相似。兩者都無法幫助 CPU 密集任務。

做出正確選擇

首先確定你的瓶頸是 I/O 還是 CPU。如果是 I/O 且需要高並行,請使用 asyncio。如果是 I/O 但處理阻塞函式庫或較簡單的腳本,執行緒是可靠的選擇。對於 CPU 密集工作,請使用多程序。

記住,你通常可以結合這些模型——例如,使用 asyncio 進行網路操作,並使用執行緒池進行檔案 I/O。關鍵是了解權衡並選擇適合你特定問題的工具。

當你需要快速格式化或驗證並行 API 呼叫返回的 JSON 資料時,試試我們的 JSON Formatter,輕鬆美化和除錯。