go-concurrency について
Go 開発者向けの並行処理スキルです。ゴルーチンのライフサイクル、チャネルや同期プリミティブの選択、並行安全性の文書化について指針を示します。
主な機能
- 01ゴルーチンのライフサイクル管理
- 02チャネルとミューテックスの選択
- 03チャネル方向とバッファの指針
- 04アトミック操作の実践
- 05並行処理の文書化指針
活用シーン
- 01停止と完了待ちが可能なゴルーチンの設計
- 02共有状態に使う同期方式の選択
- 03並行タスクのキャンセルと連携の計画
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ cxuu/ golang-skills --skill go-concurrencyインストール後、Claude Code で/go-concurrencyを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/ADVANCED-PATTERNS.mdBUFFER-POOLING.mdGOROUTINE-PATTERNS.mdSYNC-PRIMITIVES.md詳細
Go Concurrency
互換性:アトミック操作の例では、利用可能な場合は標準ライブラリの型付きアトミックを使用できます。また、プロジェクトですでに依存している場合は
go.uber.org/atomicを使用できます。
リソースの振り分け
references/GOROUTINE-PATTERNS.md- ゴルーチンの開始、停止、または待機を行うときに読んでください。references/SYNC-PRIMITIVES.md- ミューテックス、アトミック、チャネル、Once 系プリミティブのどれを選ぶか検討するときに読んでください。references/BUFFER-POOLING.md- チャネルを使った再利用やsync.Pool形式の再利用を検討するときに読んでください。references/ADVANCED-PATTERNS.md- ワーカープール、パイプライン、errgroup、キャンセルを多用するパターンについて読むときに参照してください。
ゴルーチンのライフタイム
規範:ゴルーチンを起動するときは、いつ終了するのか、あるいは終了するのかどうかを明確にしてください。
ゴルーチンは、チャネルの送受信でブロックされてリークすることがあります。チャネルを参照するほかのゴルーチンが存在しなくても、GC がブロック中のゴルーチンを終了させることはありません。リークしていない実行中のゴルーチンであっても、パニック(クローズ済みチャネルへの送信)、データ競合、メモリの問題、リソースリークを引き起こすことがあります。
基本ルール
- すべてのゴルーチンに停止手段が必要です — 予測可能な終了時刻、キャンセルシグナル、またはその両方を用意します
- ゴルーチンの終了を待てるようにする必要があります
init()内でゴルーチンを起動しないでください — 代わりにライフサイクルメソッド(Close、Stop、Shutdown)を公開します- 同期処理の範囲を限定します — 関数のスコープ内に収め、ロジックを同期関数に切り分けます
// Good: Clear lifetime with WaitGroup.Go (Go 1.25+)
var wg sync.WaitGroup
for item := range queue {
item := item
wg.Go(func() { process(ctx, item) })
}
wg.Wait()
// Bad: No way to stop or wait
go func() { for { flush(); time.Sleep(delay) } }()
go.uber.org/goleak を使ってリークをテストしてください。
原則:停止方法がわからないままゴルーチンを起動してはいけません。
通信による共有
「メモリを共有して通信するのではなく、通信によってメモリを共有しましょう。」
これは Go の並行処理設計における基本原則です。あるゴルーチンが値を生成し、別のゴルーチンがそれを消費する場合は、所有権の移譲や処理の調整にチャネルを使用します。複数のゴルーチンが共有状態にアクセスし、チャネルでは不必要に複雑になる場合は、ミューテックスを使用します。
チャネルをデフォルトにします。 ゴルーチン間でデータを受け渡すのではなく、共有データ構造(キャッシュやカウンターなど)の保護が本質となる問題では、sync.Mutex / sync.RWMutex を使用します。
同期関数
規範:非同期関数より同期関数を優先してください。
| 利点 | 理由 |
|---|---|
| ゴルーチンの範囲が限定される | ライフタイムを把握しやすくなります |
| リークや競合を回避できる | リソースリークやデータ競合を防ぎやすくなります |
| テストが容易になる | ポーリングせずに入出力を確認できます |
| 呼び出し側の柔軟性 | 必要なときに呼び出し側で並行処理を追加できます |
推奨:呼び出し側で不要な並行処理を取り除くのは、かなり難しい(場合によっては不可能な)ことです。必要に応じて、呼び出し側で並行処理を追加できるようにしてください。
ゼロ値のミューテックス
sync.Mutex と sync.RWMutex のゼロ値は有効です。ミューテックスへのポインターが必要になることは、ほとんどありません。
// Good: Zero-value is valid // Bad: Unnecessary pointer
var mu sync.Mutex mu := new(sync.Mutex)
ミューテックスを埋め込まないでください — Lock / Unlock を公開 API ではなく実装の詳細として扱えるよう、名前付きの mu フィールドを使用します。
チャネルの方向
規範:可能な場合はチャネルの方向を指定してください。
方向を指定すると、エラーを防ぎ(コンパイラーが受信専用チャネルのクローズを検出します)、所有権を明示でき、コードの自己文書化にもつながります。
func produce(out chan<- int) { /* send-only */ }
func consume(in <-chan int) { /* receive-only */ }
func transform(in <-chan int, out chan<- int) { /* both */ }
チャネルのサイズ:1 または 0
チャネルのサイズは0(バッファなし)または1にしてください。それ以外のサイズにする場合は、次の点を説明する必要があります。
- サイズをどのように決定したか
- 負荷がかかったときにチャネルが満杯にならないようにする仕組み
- 書き込み側がブロックした場合に何が起きるか
c := make(chan int) // unbuffered — Good
c := make(chan int, 1) // size one — Good
c := make(chan int, 64) // arbitrary — needs justification
アトミック操作
型安全なアトミック操作には、atomic.Bool、atomic.Int64 などを使用してください(Go 1.19 以降の標準ライブラリ sync/atomic、またはgo.uber.org/atomic)。生の int32 / int64 フィールドでは、一部のコードパスでアトミックアクセスを忘れやすくなります。
// Good: Type-safe // Bad: Easy to forget
var running atomic.Bool var running int32 // atomic
running.Store(true) atomic.StoreInt32(&running, 1)
running.Load() running == 1 // race!
並行処理のドキュメント化
注意:操作の種類からスレッドセーフかどうかが明らかでない場合は、スレッドセーフ性をドキュメントに記載してください。
Go のユーザーは、読み取り専用の操作は並行して安全に使用でき、変更を伴う操作はそうではないと考えます。次の場合は、並行処理に関する説明を記載してください。
- 読み取りと変更のどちらかが不明確 — たとえば、LRU の状態を変更する
Lookup - API が同期を提供する — たとえば、スレッドセーフなクライアント
- インターフェースに並行処理の要件がある — 型定義に記載してください
Context の使用
context.Context のガイダンス(パラメーターの配置、構造体への格納、カスタム型、派生パターン)については、専用の go-context スキルを参照してください。
チャネルを使ったバッファプーリング
バッファーを再利用するには、空きリストとしてバッファー付きチャネルを使用します。この「リーキーバッファー」パターンでは、ノンブロッキング操作に select と default を使用します。
関連スキル
- Context の伝播:ゴルーチン間でキャンセル、期限、リクエストスコープの値を受け渡す場合は、go-context を参照してください
- エラー処理:ゴルーチンからエラーを伝播させる場合や errgroup を使用する場合は、go-error-handling を参照してください
- 防御的な堅牢化:API 境界で共有状態を保護する場合や、クリーンアップに defer を使用する場合は、go-defensive を参照してください
- インターフェース設計:同期プリミティブを持つ型のレシーバー型を選ぶ場合は、go-interfaces を参照してください
外部リソース
- 停止方法が分からないままゴルーチンを開始してはならない — Dave Cheney
- 古典的な並行処理パターンを再考する — Bryan Mills (GopherCon 2018)
- Go プログラムの終了時 — Go Time ポッドキャスト
- go.uber.org/goleak — テスト用ゴルーチンリーク 検出器
- go.uber.org/atomic — 型安全な アトミック操作