iT邦幫忙

2026 iThome 鐵人賽

DAY 16
0
Software Development

下載器不是寫完就能跑:一個 HLS 分段式串流工具的架構與測試修練系列 第 16 篇

Day 16:為什麼要非同步——I/O bound 工作的線程池 vs asyncio

  • 分享至 

  • xImage
  •  

前言:下載幾十個片段,逐一循序下載不行嗎?

技術上完全可以,只是慢。如果每個片段的下載都要等前一個完成才開始下一個,總耗時大約是「單一片段耗時 × 片段數量」;但下載這種工作大部分時間都花在等待網路回應(I/O bound),CPU 幾乎閒置,讓多個下載同時進行,總耗時可以大幅壓縮到接近「單一片段耗時」的量級。

今日目標

  • 理解 I/O bound 工作為什麼特別適合非同步/並行處理
  • 認識 asyncio 跟 ThreadPoolExecutor 兩種常見選項的差異
  • 知道選擇依據不是「哪個比較潮」,是工作的性質

I/O bound vs CPU bound

判斷該不該用非同步處理的第一個問題是:這個工作花的時間,大部分是在等待外部資源(網路、磁碟),還是在真正運算? 下載片段這種工作,程式碼發出請求之後,大部分時間都在等對方回應,這段等待期間 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、線程池,還是其他機制?選擇的理由是效能考量,還是手上函式庫的限制?

今日重點回顧

  • I/O bound 工作特別適合非同步/並行處理,因為等待期間 CPU 是閒置的
  • asyncio 輕量但要求函式庫支援非同步介面,ThreadPoolExecutor 較重但能相容阻塞式介面
  • 選擇依據是工作性質跟手上函式庫的限制,不是單純的效能理論

明日預告

明天講一個真實存在、混用兩種併發模型的設計:用 ThreadPoolExecutor 包住 asyncio coroutine,這是解決什麼問題的權宜之計。


上一篇
Day 15:第二部回顧——把一個串流協定拆解成可以逐步驗證的小問題
下一篇
Day 17:ThreadPoolExecutor 包 asyncio coroutine——一個混用兩種併發模型的權宜設計
系列文
下載器不是寫完就能跑:一個 HLS 分段式串流工具的架構與測試修練 共 17 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言