為什麼需要 async
本集目標
搞懂 async 適合什麼樣的程式,並且分清楚「並行」與「平行」這兩個常被搞混的詞。
正文
async 為什麼存在
上一集的伺服器有一個特色:它大部分時間都在等。等有人連進來、等資料送達、等回應寫出去。真正用到 CPU 計算的時間少得可憐。
這種「大部分時間都在等」的程式其實非常多:
- 網路伺服器:等客戶端送請求、等資料庫回答。
- 爬蟲:送出一堆網路請求,然後等對方回。
- 資料庫查詢:送出查詢,等結果。
- 聊天室:等每個使用者輸入訊息。
- 各種背景工作:等計時器、等檔案、等別的程式。
這些程式的瓶頸不是「CPU 算不夠快」,而是「花太多時間在等」。async 就是為了這種情境而生的:它讓你的程式在等待某件事的時候,把寶貴的執行緒拿去推進別的工作。
為什麼不直接開很多執行緒就好
你可能會想:前幾章不是教過多執行緒嗎?要同時處理一萬個連線,那就開一萬條執行緒不就好了?
問題出在成本。作業系統的執行緒(OS Thread)很吃記憶體——每開一條,作業系統就得替它準備一塊 stack 空間,常常是好幾 MB。一萬條執行緒就可能吃掉好幾 GB 的記憶體,這還沒算上作業系統在這麼多執行緒之間切換的負擔。對於一個「大部分時間都在等」的程式來說,開一萬條執行緒、然後讓它們幾乎都在睡覺,實在太奢侈了。
async 的做法不一樣:它可以用少少幾條執行緒,輪流推進成千上萬個等待中的工作。每個工作不再對應一條笨重的 OS 執行緒,而是一個輕量的東西(我們之後會慢慢看到它的真面目)。這就是 async 能支撐大量連線的原因。
並行 vs 平行
這裡要把兩個很容易混淆的詞講清楚,因為它們是理解 async 的關鍵。
並行(concurrency):同時處理很多件事,但靠的是「交錯切換」。想像一位咖啡師一個人顧好幾桌客人:他幫 A 桌點完餐,趁咖啡機在沖的時候去幫 B 桌點餐,再回頭幫 A 桌收尾。任何一個瞬間他其實只在做一件事,但因為他懂得在「等」的空檔切去做別的,整體看起來好像同時服務了很多桌。一個人、一條執行緒就能做到並行。
平行(parallelism):同一個瞬間,真的有很多事一起在執行。這需要多個 CPU 核心——就像咖啡店裡站了好幾位咖啡師,每人各顧一桌,真正同一時間一起動手。
這兩件事是獨立的兩個維度,可以自由組合:
- 單執行緒的
async:有並行、沒有平行(一位咖啡師輪流服務多桌)。 - 把純計算丟到多個核心:有平行(多位咖啡師),不見得需要
async的並行。 - 多執行緒的 runtime(Tokio 預設):兩者都有(多位咖啡師,而且每位都會在空檔切去服務別桌)。
async 提供的是「並行」
關鍵結論來了:async 本身提供的是並行——它讓你用少少幾條執行緒,交錯推進一大堆正在等待的工作。至於能不能平行,那是由 runtime 決定用幾條執行緒來跑這些工作。
所以請記住:async 不會讓你的 CPU 計算變快。如果你的程式是在做一個很吃資源的數學運算,async 幫不上忙,那是平行(丟到多核心處理器上)才能解決的問題。async 解決的是另一件事——讓「等待」的時間不被浪費,在等的空檔切去做別的事。
下一集開始,我們就要把「async 到底是什麼」這件事一層一層拆開來看。
重點整理
async適合「大部分時間都在等 I/O」的程式:伺服器、爬蟲、資料庫、聊天室、背景工作- OS 執行緒很吃記憶體,用「一個連線一條執行緒」撐不住大量連線;
async用少少幾條執行緒推進大量工作 - 並行是交錯切換、同時處理很多事,一條執行緒就能做到(咖啡師比喻);平行是多核心同一瞬間一起執行
async提供的是並行,要不要平行由 runtime 用幾條執行緒決定async不會讓計算變快,它只是讓等待的時間可以拿去做別的事