問題:非確定性使除錯極度困難#
並行程式碼的執行缺乏確定性,導致:
- 無法可靠重現 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 object 或 test double
- 在內部等待回應完成,對測試程式碼提供同步的介面
fetch_file()
{
local url="$1"
local filename="$2"
wget -q -O $filename $url &
if [ "$TESTING" ] ; then
wait # 測試模式下等待完成,變成同步行為
fi
}循序啟動而非並行啟動#
- 不要同時啟動多個執行緒/任務讓它們並行運作
- 在測試配置下,逐一啟動並等待完成
投入心力建立可測試、可除錯的設定是值得的——這能降低未來錯誤的發生率,並讓修正更容易追蹤。
重點回顧#
- 使用 Humble Object Pattern 將非確定性的並行程式碼從業務邏輯中隔離出來,讓兩者分別用最適合的工具除錯
- 透過單執行緒設定、mock object 和循序執行等方式,在除錯和測試時消除非確定性
- 隔離讓非確定性核心更小、更容易推理;移除讓除錯會話變得可重複且可預測