為什麼需要專用工具#

撰寫低階多執行緒程式碼極為容易出錯,存在數十種細微的錯誤可能,而且往往只在客戶環境、重要 demo 或交付期限前才出現。使用專用工具可以大幅節省除錯時間。

靜態分析工具#

透過靜態分析(static analysis)檢查原始碼,找出可能的並行錯誤模式。

FindBugs(Java)#

  • 可偵測 “Multi-threaded Correctness” 類別下的 45 種錯誤
  • 包括:錯誤的同步化元素、wait() / notify() 呼叫不匹配、不一致的同步化、未保護的欄位、忘記釋放鎖
  • 可從命令列、GUI、IDE 或 CI 流程中執行
$ java -jar findbugs.jar -textui Counter.class
M M IS: Inconsistent synchronization of Counter.n;
  locked 60% of time
  Unsynchronized access at Counter.java:[line 9]

FindBugs 能發現 increment()synchronizeddecrement() 沒有的不一致問題——這類錯誤在 code review 中也容易被忽略。

動態分析工具#

另一類工具分析程式的執行時期行為,可以偵測 race condition、deadlock、API 誤用和效能退化。

動態分析工具會使程式慢數個數量級,且大幅增加記憶體使用量。這是為了細緻檢查多執行緒間的依賴關係所必須付出的代價。

Intel Inspector#

  • 可偵測 OpenMP 和 POSIX Threads 程式碼中的 data racedeadlock
  • 提供 GUI 介面,清楚列出問題位置與類型

以 OpenMP 計數器程式為例,Intel Inspector 找出兩個 data race:

  • 變數 i 在執行緒間錯誤共享
  • 變數 counter 未使用原子操作遞增

修正方式:用 #pragma omp threadprivate(i) 讓每個執行緒有自己的 i,並用 #pragma omp atomic 保護 counter++

Helgrind(Valgrind 工具套件)#

  • 偵測 C、C++、Fortran 中使用 POSIX Threads 的同步化錯誤
  • 能偵測潛在 deadlock(potential deadlock),即使 deadlock 在該次執行中沒有實際發生
valgrind --tool=helgrind deadlock

Helgrind 透過建立鎖取得順序的模型來偵測潛在 deadlock:

  • 觀察 bob 建立的取鎖順序為 m1 -> m2
  • 發現 alice 違反了此順序(m2 -> m1)
  • 即使程式跑了 61,266 次才實際 deadlock,Helgrind 只需一次執行就能偵測到

重點回顧#

  • 使用靜態分析工具(如 FindBugs)掃描原始碼,找出同步化和鎖定錯誤
  • 使用動態分析工具(如 Intel Inspector、Helgrind)在執行時期偵測 race condition、潛在 deadlock 和 API 誤用
  • 動態分析工具能找到指令間距離很遠的細微 bug,這是大多數靜態分析工具做不到的
  • Helgrind 透過 partial ordering 模型,即使 deadlock 未實際發生也能偵測到潛在風險