核心觀點#

物件(Objects)是「處理過程」的抽象化,
而執行緒(Threads)則是「排程」的抽象化。

平行化(Concurrency)是種解耦策略,它幫助我們拆解開「做什麼(What)」與「何時做(When)」。
在單執行緒應用中,這兩者緊密耦合;而在多執行緒環境下,程式運作就像由多台電腦同時執行,
能顯著提升系統的回應速度與吞吐量,但也帶來極高複雜度。

撰寫整潔的平行化程式碼很難。如果你以為用框架就能忽略實作細節,
或認為平行化總能改善效能,這些誤解將導致嚴重的系統災難。


平行化的迷思與挑戰#

常見誤解#

迷思事實
總是能改善效能錯。
只有在多個執行緒能獨立等待(如 I/O)或運用多處理器計算時,
效能才會提升
不需修改原設計錯。 平行化演算法的設計結構與單執行緒截然不同
依賴框架就不需了解細節錯。 如果不了解同步原語(如鎖、信號量),
即使有框架也無法偵測並排除死鎖或競爭條件

真實的挑戰#

  • 額外負擔: 上下文切換(Context Switching)與資源管理會消耗效能
  • 複雜度激增: 即便只是簡單邏輯,一旦並行執行,路徑組合將呈指數成長
  • 難以重現的 Bug: 平行化錯誤往往被視為「偶發事件」或「雜訊」,
    因它們可能執行幾千次才出現一次(Heisenbugs),這導致開發者容易忽略真正的問題

防禦策略:如何寫好平行化程式碼#

1. 單一職責原則 (SRP)#

平行化邏輯本身就夠複雜了,不該與業務邏輯混在一起。

  • 建議: 將「執行緒管理程式碼」與「主程式運作邏輯」完全分離

2. 限制資料視野 (Limit Scope of Data)#

當兩個執行緒修改同個物件時,就會發生競爭條件(Race Condition)。

  • 臨界區域 (Critical Section):synchronized 或鎖定機制保護共享物件
  • 嚴格限制: 盡可能減少臨界區域的數量與範圍
範例:解決競爭條件

修正前 (非執行緒安全): 多個執行緒同時讀取 lastIdUsed,可能導致 ID 重複。

lastIdUsed = 0


def getNextId():
    global lastIdUsed
    lastIdUsed += 1
    return lastIdUsed

修正後 (使用鎖定機制): 確保同時間只有一個執行緒能進入修改區塊。

import threading

lastIdUsed = 0
idLock = threading.Lock()


def getNextId():
    global lastIdUsed
    with idLock:  # 進入臨界區域
        lastIdUsed += 1
        return lastIdUsed

3. 使用資料副本 (Copy Data)#

避免共享資料最好的方法,就是不要共享。

  • 唯讀副本: 若資料不需寫回,讀取副本最安全
  • 獨立執行: 讓每個執行緒在自己世界(Stack)運作,不與其他執行緒共用變數

4. 了解並運用函式庫#

不要重新發明輪子。現代語言(如 Java, Python, Go)都提供了線程安全的集合。

  • 用 ConcurrentHashMap 等並行集合,而非自己鎖定 HashMap
  • 熟悉生產者-消費者(Producer-Consumer)、讀取者-寫入者(Readers-Writers)等執行模型

同步方法的注意事項#

縮小同步範圍#

  • 減少同步方法: 每個共享類別最好只有一個同步方法。如果需要多個,請重新檢視設計
  • 保持區塊簡短: synchronized 區塊會造成延遲,應盡可能讓區塊內程式碼越少越好(只包含關鍵的資料操作)

警惕「關閉」系統#

撰寫一個能正確關閉(Shut-down)的平行系統非常困難。
常見問題是父執行緒結束了,但子執行緒因死鎖或等待訊號而永遠卡住。

  • 建議: 儘早規劃關閉邏輯,不要留到最後才做

測試平行化程式碼#

測試單執行緒程式已經夠難,測試多執行緒程式更是難上加難。

心態調整: 不要將偶發失敗歸咎於「系統不穩」或「宇宙射線」。
執行緒相關的錯誤往往「千年一遇」,若忽略它們,問題將在負載最高時爆發。

推薦測試策略#

策略說明
先確保單執行緒正確先在不開啟多執行緒的情況下驗證邏輯
隨插隨用 (Pluggable)讓程式碼能在單/多執行緒環境間切換
調整系統參數 (Tunable)測試執行緒數量多於 CPU 核心數,讓隱藏的競爭條件現形
跨平台測試在不同 OS 上執行,因排程方式不同
誘導失敗 (Jiggling)動態插入 sleep(), yield() 改變執行順序

“If you think you are done, you are just getting started.”
在平行化程式設計中,只要你花心力完善程式碼並進行壓力測試,就能大幅提高找到 Bug 的機率。