技術上完全可以,只是慢。如果每個片段的下載都要等前一個完成才開始下一個,總耗時大約是「單一片段耗時 × 片段數量」;但下載這種工作大部分時間都花在等待網路回應(I/O bound),CPU 幾乎閒置,讓多個下載同時進行,總耗時可以大幅壓縮到接近「單一片段耗時」的量級。
asyncio 跟 ThreadPoolExecutor 兩種常見選項的差異判斷該不該用非同步處理的第一個問題是:這個工作花的時間,大部分是在等待外部資源(網路、磁碟),還是在真正運算? 下載片段這種工作,程式碼發出請求之後,大部分時間都在等對方回應,這段等待期間 CPU 完全閒置——這正是 I/O bound 工作的特徵,也是最適合用非同步處理換取效能的場景。
asyncio:單線程的事件迴圈,靠協程(coroutine)在等待 I/O 時把控制權讓出去,讓其他協程有機會執行。適合大量、輕量的 I/O 等待。ThreadPoolExecutor:用多個作業系統線程平行執行,每個線程各自阻塞式地等待 I/O。適合需要呼叫「本身是阻塞式介面」的函式庫時,不想或無法把它改寫成非同步版本。理論上 I/O bound 工作用 asyncio 是比較輕量的選擇——不需要為每個並行任務開一條作業系統線程,記憶體成本更低。但實務上常常卡在一個現實限制:你要呼叫的某個函式庫,本身是阻塞式介面,沒有提供非同步版本。 這種情況下,把它硬塞進 asyncio 的事件迴圈裡執行,會讓這個阻塞呼叫卡住整個事件迴圈,其他協程也無法推進——這時候 ThreadPoolExecutor 反而是務實的選擇:讓每個阻塞呼叫在自己的線程裡執行,不會拖累其他工作。
你處理過的並行任務裡,工作本質是 I/O bound 還是 CPU bound?如果是 I/O bound,你用的是 asyncio、線程池,還是其他機制?選擇的理由是效能考量,還是手上函式庫的限制?
asyncio 輕量但要求函式庫支援非同步介面,ThreadPoolExecutor 較重但能相容阻塞式介面明天講一個真實存在、混用兩種併發模型的設計:用 ThreadPoolExecutor 包住 asyncio coroutine,這是解決什麼問題的權宜之計。