為什麼使用並發 (Why Use Concurrency)

Overview Table

概念 重點
使用並發的兩大理由 分離關注點 (SoC)效能
分離關注點 相關代碼放一起、無關代碼分開;執行緒數取決於概念設計而非核心數
任務平行 (task parallelism) 單一任務拆成多部分並行 → 降低總執行時間
資料平行 (data parallelism) 各執行緒對不同資料塊相同操作 → 提升吞吐量
易並行 (embarrassingly parallel) 可隨硬體執行緒數擴展的演算法,「人多力量大」
不用並發的唯一理由 收益比不上成本(啟動開銷、資源、切換、複雜度)

分離關注點 (Separation of Concerns)

編寫軟體時,把相關的代碼放在一起、無關的代碼分開,程式更容易理解與測試、更少出錯。並發讓「必須同時進行的操作」也能各自劃入獨立的功能區域,不必自行編寫任務切換機制、或在操作中主動呼叫不相關代碼。

經典例子:DVD 播放程式(UI 密集型應用)

好處:

Important

以 SoC 為目的劃分執行緒時,執行緒數量基於概念上的設計,與 CPU 核心數無關。

效能:任務平行 vs 資料平行

晶片製造商不再拉高單核速度,而是走多核設計(單晶片 2、4、16 或更多處理器)。如 Herb Sutter 所言:「免費的午餐結束了 (The Free Lunch Is Over)」——程式不會再自動隨新處理器變快;想利用日益增長的計算能力,就必須設計多任務並發的軟體。

利用並發提升效能的兩種方式:

方式 做法 著重點 備註
任務平行 (task parallelism) 單一任務分成幾部分並行執行 降低總執行時間 看似直觀、實則複雜:各部分間可能存在依賴;可依「流程」切(執行緒各跑演算法的一部分)或依「資料」切
資料平行 (data parallelism) 每個執行緒在不同資料塊上執行相同操作 同上(任務平行依資料切分的形式) 依資料切分的任務平行
以平行解決更大的問題 一次處理 2 個、10 個、20 個檔案而非 1 個 提升吞吐量:等量資料耗時不變,但相同時間內處理更多資料 也是資料平行的一種應用;可解鎖新功能(如平行處理圖片不同區域 → 支援更高解析度影片)

什麼時候不使用並發

Important

知道何時不用並發,與知道何時使用一樣重要。不使用並發的唯一原因:收益比不上成本

成本清單:

  1. 腦力與維護成本:並發代碼常更難理解,編寫、維護成本高;複雜性增加 → 錯誤增多。除非潛在效能增益夠大、或關注點分離夠清晰,足以抵銷額外開發與維護成本,否則勿用。
  2. 執行緒啟動開銷:OS 需分配核心資源與堆疊空間、把新執行緒加入排程器,都需要時間。若任務完成得很快,任務本身耗時可能遠小於啟動執行緒的時間 → 整體效能比單執行緒還差
  3. 執行緒是有限資源:太多執行緒同時執行會耗盡 OS 資源,拖慢整個系統。每個執行緒需要獨立堆疊——32-bit(4GB 位址空間)架構下,若每執行緒 1MB 堆疊(常見預設),4096 條執行緒就用光全部位址空間(代碼、靜態資料、堆積都沒空間);64-bit 沒有這種位址空間限制,但其他資源依然有限。
  4. Context switch 成本:執行緒越多,OS 需要的上下文切換越多,每次切換都耗時——某個程度後,增加執行緒反而降低整體效能

情境:客戶端/伺服器 (C/S) 應用在伺服器端「每連線一條執行緒」——少量連線沒問題;連線量大時執行緒過多、耗盡系統資源。此時執行緒池 (thread pool) 可最佳化效能(參見第9章)。

Warning

執行緒池能限制執行緒數量,但不是靈丹妙藥——問題(資源、切換、設計複雜度)不會憑空消失。追求最佳效能時,應依硬體並發能力調整執行執行緒的數量

Tip

並發和其他最佳化策略一樣:可能大幅提升效能,也讓代碼更複雜、更難理解、更易出錯。因此只對效能關鍵部分並發化;但若收益主要在設計清晰或關注點分離,也值得採用多執行緒。


Exam/Test Patterns

情境/關鍵字 答案
「使用並發的兩大理由」 分離關注點 (SoC)效能
「UI 卡住/播放中要即時回應輸入」 用執行緒分離關注點:UI 執行緒 + 工作執行緒
「SoC 劃分執行緒,數量依據?」 概念上的設計,與 CPU 核心數無關
「把演算法拆成多部分並行」 任務平行
「不同資料塊上做相同操作」 資料平行
「時間不變但處理更多資料」 以平行解決更大的問題(吞吐量)
「隨硬體執行緒數擴展的演算法」 易並行 (embarrassingly parallel)
「免費的午餐結束了」 Herb Sutter:單核不再變快,須寫多任務並發軟體
「任務很短,開執行緒反而更慢」 啟動開銷(核心資源、堆疊、排程)大於任務本身
「32-bit + 1MB 堆疊,最多幾條執行緒」 4096 條即耗盡 4GB 位址空間
「大量連線的伺服器,每連線一執行緒」 資源耗盡 → 用執行緒池(第9章)
「何時不用並發」 收益比不上成本