問題:非確定性使除錯極度困難#

並行程式碼的執行缺乏確定性,導致:

  • 無法可靠重現 bug
  • 無法透過反覆試驗逐步縮小問題範圍
  • 無法驗證 bug 是否真正修復

除了使用 capture and replay 等極端手段外,還有兩種替代策略:隔離(isolation)和移除(removal)。

隔離策略:Humble Object Pattern#

Humble Object 模式將程式碼分成兩部分:

  • 非確定性核心:僅包含最小限度的並行程式碼
  • 確定性邏輯:其餘所有業務邏輯

好處#

  • 確定性部分可以用傳統的、成熟的工具和技術來測試與除錯
  • 非確定性核心因為很小,更容易進行形式化或半形式化推理
  • 甚至有可能證明非確定性核心沒有問題
  • 小核心也更容易用可靠的現成原語(ready-made primitives)重寫
  • 兩部分的分離為進一步的架構改善開啟大門

移除策略:消除非確定性的來源#

透過替換非確定性的元件為行為可預測的元件:

設定為單執行緒#

  • 將多執行緒元件設定為只使用單一執行緒執行
  • Java:建立 ForkJoinPool 並將 parallelism 設為 1
  • C/C++:設定 SQLite 的 SQLITE_CONFIG_SINGLETHREAD 選項
  • 這讓測試和除錯能知道預期結果,並獲得可重複的除錯會話

單執行緒設定僅用於除錯和測試,不應包含在 production 中。它讓你能排除並行問題,專注於程式的循序邏輯錯誤。

使用 Mock Object 和 Test Double#

  • 對於非同步回應的物件,建立一個 mock objecttest double
  • 在內部等待回應完成,對測試程式碼提供同步的介面
fetch_file()
{
  local url="$1"
  local filename="$2"
  wget -q -O $filename $url &
  if [ "$TESTING" ] ; then
    wait    # 測試模式下等待完成,變成同步行為
  fi
}

循序啟動而非並行啟動#

  • 不要同時啟動多個執行緒/任務讓它們並行運作
  • 在測試配置下,逐一啟動並等待完成

投入心力建立可測試、可除錯的設定是值得的——這能降低未來錯誤的發生率,並讓修正更容易追蹤。

重點回顧#

  • 使用 Humble Object Pattern 將非確定性的並行程式碼從業務邏輯中隔離出來,讓兩者分別用最適合的工具除錯
  • 透過單執行緒設定mock object循序執行等方式,在除錯和測試時消除非確定性
  • 隔離讓非確定性核心更小、更容易推理;移除讓除錯會話變得可重複且可預測