並列処理レビュー完全ガイド:やりすぎ最適化の検出と是正
並列処理レビュー完全ガイド:やりすぎ最適化の検出と是正
Goの goroutine
は極めて手軽に並列処理を書ける。しかしこの「簡単さ」がレビュー上の最大リスクになる。
並列化すれば速い、goroutineは軽い──この思い込みにレビューアーは注意を向ける必要がある。
並列処理レビューは「必要性」「粒度」「統制」の3軸を常に確認することが基本になる。
本稿では「やりすぎた並列処理」にレビューアーがどう気づき、どう指摘するべきかを整理する。
良い設計例:制御可能な並列化とは
まずは良い実装から構造を整理する。これはワーカープール型の典型例である。
func startWorkerPool(n int, jobs <-chan Job) {
for i := 0; i < n; i++ {
go func() {
for job := range jobs {
process(job)
}
}()
}
}
この設計では「同時に動くgoroutine数」が n
に限定される。
負荷が予測可能になり、サーバの安定性も大きく向上する。
レビューで並列数の制御が確認できれば、まず設計として安全圏に入る。
問題のある実装例とレビュー指摘
過剰なgoroutine展開
for _, task := range tasks {
go func(t Task) {
t.Run()
}(task)
}
@Reviewer各タスクごとに無制限にgoroutineを起動しています。処理時間が短い場合は、起動コストが処理時間を上回る可能性があります。処理粒度を考慮し、逐次実行またはワーカープールへの変更を検討してください。
「短時間処理を並列化する方が遅くなる」 というのは現場で非常によく起きる落とし穴。
並列化のコストはゼロではない。
並列数の統制がない設計
for _, item := range list {
go process(item)
}
@Reviewer並列数の上限が設計されていません。処理負荷が高まるとサーバ全体が不安定になります。チャネルやセマフォ等を使い、同時実行数を制御する設計へ修正してください。
GOMAXPROCSを無視した実装
runtime.GOMAXPROCS(4)
@Reviewer実行スレッド数は4に制限されていますが、goroutine生成数がこれを考慮せずに無制限に起動される設計になっています。スレッドスケジューラ競合を避けるため、並列数をGOMAXPROCS以下に抑制してください。
GOMAXPROCS ≠ goroutineの許容量 である。
goroutine数は「論理CPU数」ではなく「処理設計」で統制すべき。
計測なしに最適化している設計
「goroutineにすれば速くなる」「並列なら高性能」という思い込みは危険。
実際に負荷計測やベンチマークを行った上で最適化を進める必要がある。
レビューでは 「計測データありますか?」 が定番質問になる。
改善例:制御された並列処理
pool := make(chan struct{}, 4)
for _, item := range list {
pool <- struct{}{}
go func(i Item) {
defer func() { <-pool }()
process(i)
}(item)
}
@Reviewerチャネルバッファにより並列数4までに制御されています。元の無制御実装では負荷暴走の危険がありましたが、このように制御構造を導入してください。
「チャネルバッファによる並列数統制」 は実務でも安定運用で非常によく使われるパターン。
コードも読みやすく、意図も明示される。
レビュー観点チェックリスト
観点 | 内容 |
---|---|
並列化の目的が明示されているか | なぜ並列化しているのか説明できるか |
処理粒度が妥当か | 並列コストに見合った処理負荷か |
並列数の統制が設計されているか | 制御構造(チャネル・セマフォ等)があるか |
goroutine生成統制があるか | 無限goroutine生成になっていないか |
計測に基づいて設計されているか | 実測データで裏付けが取れているか |
無制御goroutineによる負荷暴走イメージ
まとめ:並列レビューは「設計目的」と「制御設計」を読み取る訓練
「並列化できる」は理由にならない。
「なぜ今これを並列化するのか」が説明できることが設計として重要。
レビューアーは常に以下を確認する:
- 並列化の設計意図が表現されているか
- 処理粒度とコストの均衡が取れているか
- 制御構造が設計に含まれているか
- 実測データに裏付けされているか