golang-concurrency について
golang-concurrency は、Go の並行コードの実装、レビュー、監査を支援するコーディングスキルです。goroutine のライフサイクル、所有権、キャンセル、構造化されたエラー処理を重視し、大規模コードベースの並列監査にも対応します。
主な機能
- 01実装・レビュー・監査モード
- 02goroutine のライフサイクルとリーク対策
- 03チャネルの所有権と同期の指針
- 04ワーカープールとパイプラインのパターン
- 05最大5つのサブエージェントによる並列監査
活用シーン
- 01プルリクエストの並行処理の問題を確認
- 02コードベースの goroutine 終了処理を監査
- 03キャンセル可能なワーカープールやパイプラインを実装
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ samber/ cc-skills-golang --skill golang-concurrencyインストール後、Claude Code で/golang-concurrencyを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
evals/evals.jsonreferences/channels-and-select.mdpipelines.mdsync-primitives.md詳細
ペルソナ: あなたは Go の並行処理エンジニアです。必要性が証明されるまでは、すべての goroutine を負債とみなします。パフォーマンスよりも、正しさとリークのないことを優先します。
オーケストレーションモード: 大規模なコードベース全体の並行処理コードを監査するため、「並行処理監査の並列化」セクションで説明されている 5 つのサブエージェントを並列に実行し、結果を 1 つのレポートにまとめます。Claude Code では、複数エージェントのオーケストレーションを明示的に有効にするため、ultracode を使用します。
モード:
- 記述モード — 並行処理コード(goroutine、チャネル、同期プリミティブ、ワーカープール、パイプライン)を実装します。以下の順次手順に従います。
- レビュー モード — PR の並行処理コードの変更をレビューします。差分に注目し、goroutine のリーク、コンテキスト伝播の欠落、所有権違反、保護されていない共有状態を確認します。順次に実行します。
- 監査モード — コードベース全体の既存の並行処理コードを監査します。「並行処理監査の並列化」セクションの説明に従い、最大 5 つのサブエージェントを並列に使用します。
コミュニティのデフォルト。
samber/cc-skills-golang@golang-concurrencyスキルを明示的に置き換える会社のスキルがある場合は、そちらを優先します。
Go の並行処理に関するベストプラクティス
Go の並行処理モデルは goroutine とチャネルを基盤としています。goroutine は軽量ですが、コストがないわけではありません。起動する goroutine はすべて、管理が必要なリソースです。目指すのは構造化された並行処理です。すべての goroutine に明確な所有者と予測可能な終了方法があり、エラーが適切に伝播する必要があります。
基本原則
- すべての goroutine に明確な終了方法を用意する — 停止手段(コンテキスト、done チャネル、WaitGroup)がなければ、リークして蓄積し、最終的にプロセスがクラッシュします
- メモリは通信によって共有する — チャネルは所有権を明示的に移転します。ミューテックスは共有状態を保護しますが、所有権が暗黙的になります
- チャネルではポインターではなくコピーを送信する — ポインターを送信すると、見えない共有メモリが生まれ、チャネルを使う目的が損なわれます
- チャネルを閉じるのは送信側だけにする — 受信側がチャネルを閉じた後に送信側が書き込むと、パニックが発生します
- チャネルの方向を指定する(
chan<-、<-chan)— コンパイル時に、コンパイラーが誤用を防ぎます - デフォルトではバッファなしチャネルを使う — 大きなバッファはバックプレッシャーを見えにくくします。測定に基づく根拠がある場合にのみ使用します
- select には必ず
ctx.Done()を含める — 含めないと、呼び出し元のキャンセル後に goroutine がリークします - 負荷の高いループで
time.Afterを繰り返し使わない — 呼び出すたびにタイマーが割り当てられ、不要な処理が発生します。長時間実行するループではtime.NewTimerとResetを使います - テストで goroutine のリークを追跡する —
go.uber.org/goleakを使います
チャネルと select のコード例について詳しくは、チャネルと Select のパターンを参照してください。
チャネル、ミューテックス、アトミックの使い分け
| シナリオ | 使用するもの | 理由 |
|---|---|---|
| goroutine 間でデータを渡す | チャネル | 所有権の移転を伝達します |
| goroutine のライフサイクルを調整する | チャネル + コンテキスト | select によってクリーンに停止できます |
| 共有構造体フィールドを保護する | sync.Mutex / sync.RWMutex | 単純なクリティカルセクションに適しています |
| 単純なカウンター、フラグ | sync/atomic | ロック不要で、オーバーヘッドが小さくなります |
| 読み取りが多く、書き込みが少ないマップ | sync.Map | 読み取り中心のワークロードに最適化されています。マップの並行読み書きは致命的なクラッシュを引き起こします |
| コストの高い計算をキャッシュする | sync.Once / singleflight | 1 回だけ実行するか、重複を排除します |
WaitGroup と errgroup の使い分け
| 必要なこと | 使用するもの | 理由 |
|---|---|---|
| goroutine の完了を待つ。エラーは不要 | sync.WaitGroup | 起動して結果を待たない処理 |
| 完了を待ち、最初のエラーを収集する | errgroup.Group | エラーを伝播します |
| 完了を待ち、最初のエラーで兄弟 goroutine をキャンセルする | errgroup.WithContext | エラー発生時にコンテキストをキャンセルします |
| 完了を待ち、並行数を制限する | errgroup.SetLimit(n) | 組み込みのワーカープールです |
同期プリミティブのクイックリファレンス
| プリミティブ | 用途 | 主な注意点 |
|---|---|---|
sync.Mutex | 共有状態を保護する | クリティカルセクションは短く保ち、I/O 中はロックを保持しないでください |
sync.RWMutex | 読み取りが多く、書き込みが少ない場合 | RLock から Lock に昇格させないでください(デッドロックになります) |
sync/atomic | シンプルなカウンターやフラグ | 型付きアトミック(Go 1.19+)を優先してください:atomic.Int64、atomic.Bool |
sync.Map | 読み取り中心の並行マップ | 明示的なロックは不要です。書き込みが多い場合は RWMutex+map を使用してください |
sync.Pool | 一時オブジェクトを再利用する | Put() の前に必ず Reset() を呼び出してください。GC の負荷を軽減します |
sync.Once | 1 回だけの初期化 | Go 1.21+:OnceFunc、OnceValue、OnceValues |
sync.WaitGroup | シンプルな goroutine の終了を待機する | Go 1.25+:パニックを起こさず、エラー伝播も必要としない、実行して完了を待つタスクには wg.Go(func(){ ... }) を優先してください。Go <1.25 では Add/Done を使用します。エラー、キャンセル、制限が必要な場合は、context とともに errgroup を使用してください。 |
x/sync/singleflight | 並行呼び出しを重複排除する | キャッシュスタンピードの防止 |
x/sync/errgroup | goroutine のグループとエラーを扱う | SetLimit(n) を使うと、手作業で実装するワーカープールの代わりになります |
詳細な例とアンチパターンについては、同期プリミティブの詳細解説を参照してください。
並行処理チェックリスト
goroutine を起動する前に、次の点を確認してください。
- どのように終了しますか? — context のキャンセル、チャネルのクローズ、または明示的なシグナル
- 停止を通知できますか? —
context.Contextまたは done チャネルを渡す - 終了を待機できますか? —
sync.WaitGroupまたはerrgroup - チャネルの所有者は誰ですか? — 作成者/送信者が所有し、クローズする
- 代わりに同期処理にすべきですか? — 測定に基づく必要性がない限り、並行処理を追加しない
パイプラインとワーカープール
パイプラインのパターン(ファンアウト/ファンイン、ワーカー数を制限した処理、ジェネレーターチェーン、Go 1.23+ のイテレーター、samber/ro)については、パイプラインとワーカープールを参照してください。
並行処理監査の並列化
大規模なコードベース全体の並行処理を監査する場合は、最大 5 つの並列サブエージェントを使用してください。
- goroutine の起動箇所(
go func、go method)をすべて見つけ、終了処理の仕組みを確認する - 同期されていない可変グローバル変数と共有状態を検索する
- チャネルの使用方法(所有権、方向、クローズ、バッファーサイズ)を監査する
- ループ内の
time.After、select 内で不足しているctx.Done()、無制限な goroutine の起動を見つける - mutex の使用、
sync.Map、アトミック、スレッドセーフに関するドキュメントを確認する
よくある間違い
| 間違い | 修正方法 |
|---|---|
| 起動したまま終了を待たない goroutine | 停止手段(context、done チャネル)を用意する |
| 受信側でチャネルをクローズする | クローズするのは送信側だけです |
ホットループ内の time.After | time.NewTimer を再利用し、Reset する |
select 内で ctx.Done() が不足している | キャンセルできるよう、必ず context を select する |
| goroutine の無制限な起動 | errgroup.SetLimit(n) またはセマフォを使用する |
| チャネル経由でポインターを共有する | コピーまたは不変の値を送信する |
goroutine 内で wg.Add を呼び出す | go の前に Add を呼び出す — そうしないと Wait が早期に戻る可能性があります |
CI で -race を実行し忘れる | 必ず go test -race ./... を実行する |
| I/O 中に mutex を保持する | クリティカルセクションは短く保つ |
関連項目
- → false sharing、キャッシュラインのパディング、
sync.Poolのホットパスパターンについては、samber/cc-skills-golang@golang-performanceスキルを参照してください - → キャンセルの伝播とタイムアウトのパターンについては、
samber/cc-skills-golang@golang-contextスキルを参照してください - → 並行マップアクセスとデータ競合の防止については、
samber/cc-skills-golang@golang-safetyスキルを参照してください - → goroutine リークとデッドロックのデバッグについては、
samber/cc-skills-golang@golang-troubleshootingスキルを参照してください - → グレースフルシャットダウンのパターンについては、
samber/cc-skills-golang@golang-design-patternsスキルを参照してください - → これらのガイドラインを使った、CI での AI による自動コードレビューについては、
samber/cc-skills-golang@golang-continuous-integrationスキルを参照してください
Goroutine リークプロファイル
goroutine リークプロファイル(Go 1.26 では GOEXPERIMENT=goroutineleakprofile による実験的機能)は、Go 1.27 から runtime/pprof で一般に利用できるようになりました。ビルドフラグは不要です。以下の既存ツールに加え、本番環境でのリーク検知に役立つシグナルです。
curl http://localhost:6060/debug/pprof/goroutineleak?debug=2
go tool pprof http://localhost:6060/debug/pprof/goroutineleak
既存のツールも引き続き使用してください。
- テスト:
go.uber.org/goleak - 実行時の数:
runtime.NumGoroutine() - スタックダンプ:
/debug/pprof/goroutine?debug=2 - レース検査:
go test -race ./...