Asynchronous Programming - 第九章:平行之風,併發之歌
有些風,獨自穿梭林間,沿著樹影與地面爬行,沒有分身,沒有回音,只將時間吹得靜謐。
有些風,輕輕裂成數萬縷絲線,從山脊滲入溪谷,從枝頭滲入土壤,它們彼此不糾纏,卻同時為大地帶來不同的聲響。
也有些種子,懂得在雲後潛伏,等待雨季將它們喚醒;一顆未必只能開一朵花,它可以把等待切碎,併發成無數顆更小的種子,在時間的縫隙裡生根發芽。
當我們談論分割與合流,等待與釋放,這片風與影子的地圖,就是平行之風,併發之歌。
那些藏在核心深處的秘密,終究會在一次又一次的呼吸裡,被我們拆解、重組、散播,直到有一天,學會怎樣用一秒去換取另一秒。
用.NET展現多核威力(1) - 從ThreadPool翻船談起
🧪 實驗
這個實驗想要透過一個簡單的數學運算(Math.Log10),用 單執行緒(Single Thread)、平行迴圈(Parallel.For)、以及 Task-based 併發(async/await + Task.WhenAll) 三種方式,分別執行相同的 100 萬次運算,並多輪重複測量執行時間。
目的是比較:
順序執行 vs. 多核心平行 vs. 多任務併發 在 CPU-bound 工作下的效能差異,瞭解平行化與併發在沒有 I/O 等待、沒有共享資源時,會帶來什麼開銷與優勢,體會 ThreadPool、Context Switching、Task 排程等背後的隱藏成本
這能幫助我們釐清:
- 什麼情況適合單執行緒?
- 什麼情況可以用平行迴圈發揮多核心效能?
- 什麼情況下 Task-based 非但沒好處還可能更慢?
1 |
|
結果
1 | This is Round : 1 |
🎵 分析
Single Thread
結果: 單執行緒平均時間第一次 4 但後續皆 ~ 30 毫秒,執行穩定。
單執行緒沒有額外的排程、執行緒切換、或執行緒池分派開銷,CPU 只要照順序完成迴圈裡的計算即可。這種情況下,效能幾乎只受到 CPU 時脈與記憶體快取的影響。適合執行小規模、簡單且不需要分拆的 CPU 任務,像是短時間的批次運算、輕量的邏輯處理,或需要維持執行緒上下文一致性時(例如需要在 UI 執行緒中執行)。
Parallel.For
平均時間約 6 ~ 11 毫秒,明顯比單執行緒快 2~5 倍。
如果你用一般的 for 迴圈,就是一次做一個,一個做完再做下一個。但有些工作是可以 分開同時做 的,像是:
- 計算一堆獨立的數學題
- 處理一大堆圖片
- 分析很多筆資料
如果一次只用一條線(CPU 的一個核心)去做,就很浪費電腦的多核心能力。Parallel.For 會把迴圈工作,切成一小塊一小塊,丟給不同的核心同時去做。
- 一般 for:一個人做 100 件事。
- Parallel.For:10 個人分工,每人做 10 件事,大家同時開始。這樣就能用到 CPU 的多核心,達到 平行處理,速度通常會更快。
本質上 Parallel.For 用的是 ThreadPool(執行緒池) 和 Task 的概念。
1️⃣ 把迴圈分段(Partitioning)
Parallel.For 會把迴圈切成多塊,比如 0999 分成 099、100~199、…,每塊給一個執行緒去做。
2️⃣ 排程分配(Thread Scheduling)
它用 ThreadPool,把這些工作分給空閒的執行緒跑,避免你自己開一大堆執行緒(那樣開太多會拖垮效能)。
3️⃣ 動態調整(Work-Stealing)
如果某個執行緒做完了,它還可以幫忙搶別人的工作來做,讓 CPU 不浪費時間。
如果每筆任務非常小,平行化的好處會被 Context Switching、排程、資料同步的成本吃掉。如果每筆任務彼此之間需要共享資源(例如對同一個集合寫入),就必須加鎖(lock),加鎖就會導致多執行緒之間搶鎖,效能會急速下降。
Task-based (Concurrency)
結果: 平均時間約 69 ~ 85 毫秒,遠遠落後於單執行緒與 Parallel.For。
Task 是 併發(Concurrency) 的抽象,它是為了讓一個執行緒可以同時「管理」多個任務進度而存在,而不是要讓每個任務都佔用獨立執行緒。在典型的 I/O Bound 情境(例如呼叫 API、等待檔案寫入),Task 透過 async/await 可以在等待期間「釋放執行緒」,讓執行緒去跑其他工作,等結果回來時再接手後續邏輯,這樣就能極大化 ThreadPool 的效益。
但在這個案例,每次都要建立一個獨立的 Task,對於 100 萬次來說,就會產生 100 萬個 Task 實例!
每個 Task 都需要:
- 分配記憶體給 Task 物件
- 加進 ThreadPool 的排程佇列
- 執行完還要觸發 Completion 回調
重點是,這些任務本身極短,完全沒有 I/O 等待,也沒時間釋放執行緒,反而產生了巨大的排程與 Context Switching 開銷。
🎵 結語
有的風,獨自輕掠,循序而行,穩定、可控;
有的風,裂成無數股氣流,沿著核心四散流動,各自翻湧,互不干擾,卻又匯成更大的動能;
而有些風,懂得在等待裡隱忍,把執行緒還給大地,讓一段封鎖的時間,換取更多並存的可能。
哪陣風適合獨行,哪陣風能劃分成絲線穿梭,哪陣風需要暫時潛伏、留白,等待時機到來。
這就是 平行之風,併發之歌