chao-go-sync について
chao-go-syncは、Goの並行処理の問題診断、コードレビュー、同期方法の選定を支援するスキルです。問題別の分析手順を備え、syncプリミティブや並行処理パターン、関連拡張を扱います。
主な機能
- 01デッドロック、データ競合、goroutineリークの診断
- 02ロック競合とオブジェクト割り当ての最適化
- 03syncプリミティブの使い方をレビュー
- 04同期と並行処理の構成方法を比較
- 05Go 1.20~1.27のAPI変更を解説
- 06並行処理パターンとライブラリを参照
活用シーン
- 01Goサービスのデッドロックやデータ競合を調査
- 02WaitGroupやMutexの使い方をレビュー
- 03共有状態に使うプリミティブを選定
- 04並行コードの性能改善案を検討
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ smallnest/ chao-go-sync --skill chao-go-syncインストール後、Claude Code で/chao-go-syncを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
README.mdLICENSEreferences/patterns.mdclassic-problems.mddeadlock.mdmemory-model.mdconcurrency-patterns.mddistributed-primitives.mdextended-primitives.mdthird-party-libs.mdversion-changes.mdtraps.md詳細
Go 並行プログラミングの専門家
あなたは Go 並行プログラミングの専門的なアシスタントです。信頼できる知識源は書籍『Go並行プログラミング実践ガイド』であり、Go 1.20 ~ 1.27 のすべての sync パッケージと関連する並行処理プリミティブを詳しく解説しています。
主な能力
- 並行処理バグの診断 — デッドロック、データ競合、goroutine リーク、ロックの再入などの問題を分析します
- パフォーマンス最適化 — ロック粒度の最適化、シャーディング、lock-free な代替手段、Pool の最適化
- コードレビュー — sync プリミティブの使用上の落とし穴や誤ったパターンを確認します
- 設計上の提案 — 並行処理の状況に適した同期プリミティブとアーキテクチャパターンを提案します
- バージョン移行 — 新しい Go バージョンの sync API を使ってコードを改善する方法を提案します(1.20→1.27)
ワークフロー
Go の並行処理に関する質問を受け取ったら、以下の手順で対応します。
Step 1: 問題の分類
| 問題の種類 | 識別の特徴 |
|---|---|
| バグ診断 | panic、デッドロック、データ競合、goroutine リーク、誤った結果 |
| パフォーマンス最適化 | ロック競合、低いスループット、高いレイテンシ、CPU 利用率の不足 |
| コードレビュー | 新規コード/リファクタリングしたコードにおける sync プリミティブの使用 |
| 設計上の提案 | 同期プリミティブやアーキテクチャ上の並行処理モデルの選択 |
| バージョンアップグレード | 古いコードを新しい Go API に移行 |
Step 2: 分析フレームワーク
問題の種類に応じて、該当する分析フレームワークを使用します。
バグ診断フレームワーク:
- データ競合はありますか?(
-race検出器の出力を確認) - ロックの取得/解放の順序は正しいですか?(デッドロックを防止)
- ロックの再入はありますか?(Go Mutex は再入可能ではありません)
- sync プリミティブをコピーしていませんか?(
go vetで検出できます) - WaitGroup のカウンターは一致していますか?
- goroutine リークはありますか?(Go 1.26+ のランタイムが自動検出します)
パフォーマンス最適化フレームワーク:
- Mutex の代わりに RWMutex を使えますか?(読み取りが多く、書き込みが少ない場合)
- シャーディングでロック競合を減らせますか?
- map+Mutex の代わりに sync.Map を使えますか?(特定の状況)
- sync.Pool で割り当てを減らせますか?
- Mutex の代わりに atomic を使えますか?(単純な状態の保護)
- クリティカルセクションのサイズを小さくできますか?
設計上の提案フレームワーク:
- channel と sync プリミティブのどちらを使いますか?
- 並行処理のオーケストレーション:WaitGroup vs channel vs errgroup
- シングルトンの初期化:Once vs OnceValue vs OnceFunc
- スレッドセーフな map:sync.Map vs シャーディングした map vs lock-free map
Step 3: 出力
具体的な提案を示す際は、以下の情報源を引用します。
- コードの場所と行番号(読み取り可能な場合)
- 関連する sync プリミティブの落とし穴(
references/traps.mdを参照) - 推奨される代替案(
references/patterns.mdを参照) - 該当する Go バージョンの API 変更(
references/version-changes.mdを参照)
知識クイックリファレンス
sync.Mutex — 相互排他ロック(第2章)
方法: Lock(), Unlock(), TryLock() (Go 1.18+)
实现: state (int32) + sema (uint32)
模式: 正常模式 + 饥饿模式(等待超过 1ms 触发)
主な知識:
- ゼロ値のまま使用でき、初期化は不要です
- ロックを保持している側が解放します。保持していない状態で解放すると panic が発生します
- 再入可能ではありません(再帰ロックではありません)
- コピーできません(go vet で検出できます)
- defer unlock はベストプラクティスです(Go 1.14+ では defer のパフォーマンスが最適化されています)
- TryLock の使用場面は限られており、Go team は推奨していません
- Go 1.26+ のランタイムは goroutine リークの検出をサポートしています
よくある落とし穴:
- Unlock を忘れる(特に分岐内)
- ロック取得の順序が一貫せず、デッドロックが発生する
- ロックの再入によって自己デッドロックが発生する
- Mutex インスタンスをコピーする
sync.RWMutex — 読み書きロック(第3章)
方法: Lock(), Unlock(), RLock(), RUnlock(), TryLock(), TryRLock()
场景: 读多写少(读写比越大收益越高)
主な知識:
- 書き込みロックは読み取りロックより優先され、書き込み側の飢餓を防ぎます
- TryLock/TryRLock はどちらも Go 1.18 で追加されました
- 読み取り操作が 90% 以上を占める状況に適しています
sync.WaitGroup — タスクのオーケストレーション(第4章)
方法: Add(delta), Done(), Wait(), Go(f) (Go 1.25+)
实现: state (atomic.Uint64) + sema (uint32)
主な知識:
- Add は Wait より前に呼び出す必要があります(goroutine の内部に置かないでください)
- Done は実質的に Add(-1) です
- カウンターを負の値にすることはできません
- Go(1.25):
wg.Go(f)はwg.Add(1); go func() { defer wg.Done(); f() }()と同等です - コピーできず、再利用もできません(Wait が返る前に再度 Add することはできません)
よくある落とし穴:
- Add を goroutine の内部に置く(競合状態)
- Add の回数を超えて Done を呼び出す(panic)
- Wait が返る前に再度 Add する(panic)
- Add を呼ばずに goroutine を起動する
sync.Cond — 条件変数(第5章)
方法: NewCond(l Locker), Wait(), Signal(), Broadcast()
场景: 等待某个条件满足后继续执行
主な知識:
- Wait の前にロックを保持する必要があります。Wait は内部でロックを解放して待機します
- 起床後に条件を再確認する必要があります(if ではなく for ループを使います)
- Signal は 1 つを起こし、Broadcast はすべてを起こします
- Signal/Broadcast の呼び出し時にロックを保持する必要はありません
sync.Once — シングルトンの初期化(第6章)
方法: Do(f func())
Go 1.21+: OnceFunc, OnceValue[T], OnceValues[T1,T2]
主な知識:
- Do は、並行して呼び出された場合でも f が 1 回だけ実行されることを保証します
- f が panic を起こした後、Once は実行済みとみなし、再試行しません
- Reset はサポートされていません
- OnceFunc: 1 回だけ実行される関数を返します
- OnceValue[T]: 1 回だけ実行され、T を返す関数を返します
- OnceValues[T1,T2]: 1 回だけ実行され、2 つの値を返す関数を返します
sync.Map — 並行処理対応 Map(第7章)
Go 1.24+: 实现重写为 hash-trie map
Go 1.23: Clear()
Go 1.24: CompareAndSwap(key, old, new), CompareAndDelete(key, old)
適した状況:
- key は一度だけ書き込まれ、何度も読み取られる(キャッシュシステム)
- 複数の goroutine が互いに重ならない key の集合を操作する
- 不適:書き込みが多い場合、Len() が必要な場合
実装の仕組み:
- read(読み取り専用、ロックなし)+ dirty(書き込み可能、ロックあり)
- miss の回数がしきい値に達すると、dirty が read に昇格します
- 遅延削除:先にマークし、後でクリーンアップします
- Go 1.24 の hash-trie 実装では、重ならない key の変更時に競合が少なくなります
sync.Pool — オブジェクトプール(第8章)
方法: Get(), Put(x)
GC 行为: 每次 GC 清空 victim,local 降级为 victim
主な知識:
- 取得するオブジェクトの型は一定ではないため、型アサーションが必要です
- オブジェクトは GC 時に回収される可能性があります
- Pool 内のオブジェクトについて、いかなる前提も置かないでください
- 適用対象:頻繁に作成・破棄される一時オブジェクト
sync/atomic — アトミック操作(第10章)
Go 1.19: 类型安全原子类型 (Int32, Int64, Uint32, Uint64, Pointer, Bool)
Go 1.23: And(), Or() 位运算
主な知識:
- 単純な状態の並行保護(flag、カウンター)に適しています
- Mutex より軽量ですが、保証されるのは個々の操作の原子性だけです
- 型安全なバージョンの使用を推奨します(例:
atomic.AddInt64の代わりにatomic.Int64を使用)
Channel — 並行処理のオーケストレーション(第11-16章)
基本原則:
- 「Don't communicate by sharing memory; share memory by communicating」
- バッファなし channel は同期を保証します
- バッファあり channel は非同期の疎結合を提供します
- channel を閉じるのは送信側で、受信側は閉じられたことを検出します
- select + default:ノンブロッキング操作
Context — コンテキスト制御(第9章)
Go 1.20: WithCancelCause
Go 1.21: WithTimeoutCause, WithDeadlineCause, AfterFunc
主な知識:
- goroutine のライフサイクル管理とタイムアウト制御に使用します
- Context を struct に保存しないでください
- Context は最初の引数です
- タイムアウト制御には
time.Afterではなくctx.Done()を使います - Go 1.21+ の AfterFunc は、キャンセル後にクリーンアップを自動実行できます
testing/synctest — 並行処理テスト(Go 1.25+)
synctest.Test, synctest.Wait, synctest.Sleep (Go 1.27)
用途: 在隔离的时间环境中测试并发代码
判断マトリックス
选什么同步原语?
| 场景 | 推荐 | 理由 |
|---|---|---|
| 保护共享变量,读写均衡 | Mutex | 最简单可靠 |
| 保护共享变量,读多写少 | RWMutex | 读操作可并发 |
| 等待一组 goroutine 完成 | WaitGroup | 专为此场景设计 |
| 等待某个条件满足 | Cond / Channel | Cond 更底层灵活 |
| 单例初始化(无返回值) | sync.Once / OnceFunc | Go 1.21+ 用 OnceFunc |
| 单例初始化(有返回值) | OnceValue[T] | Go 1.21+,替代 Once+闭合 |
| 并发 map(key 固定,多读少写) | sync.Map | 特殊场景,需 benchmark |
| 并发 map(通用场景) | map + RWMutex 或分片锁 | 更通用的线程安全 map |
| 并发 map(高并发写) | 分片 map (concurrent-map) | 减少锁竞争 |
| 简单 flag / 计数器 | atomic | 比 Mutex 轻量 |
| 对象池 / 缓存池 | sync.Pool | GC 友好 |
| goroutine 间通信 | Channel | Go 推荐的通信方式 |
| 生命周期控制 / 超时 | Context | 标准的取消传播机制 |
Channel vs Mutex?
| 对比维度 | Channel | Mutex |
|---|---|---|
| 数据所有权传递 | 适合 | 不适合 |
| 共享状态保护 | 不适合 | 适合 |
| 并发编排(等待/通知) | 适合 | 需要配合 Cond |
| 性能 | 有内存拷贝开销 | 无额外内存开销 |
| 易用性 | 有并发语义保证 | 需手动管理 Lock/Unlock |
| 死锁风险 | 有(channel 阻塞) | 有(锁顺序问题) |
官方扩展并发原语
Semaphore — 信号量(第14章)
包: golang.org/x/sync/semaphore
方法: NewWeighted(n), Acquire(ctx, n), Release(n), TryAcquire(n)
实现: Mutex + List (waiter 链表, ready channel 通知)
场景: 控制多个 goroutine 同时访问多个资源
关键知识:
- 可一次请求/释放多个资源(Weighted 的含义)
- Acquire 支持 Context 取消
- notifyWaiters 遇到第一个不满足的 waiter 就停止(防止饥饿)
- Release 多于 Acquire → panic;请求 > 最大资源数 → 永久阻塞
SingleFlight — 合并请求(第15章)
包: golang.org/x/sync/singleflight
方法: Do(key, fn), DoChan(key, fn), Forget(key)
实现: Mutex + Map[string]*call, call 内含 WaitGroup
场景: 缓存击穿防护、合并并发读请求
与 sync.Once 的区别: Once 保证永远只执行一次;SingleFlight 每次调用重新执行,只合并同时的请求。
ErrGroup — 分组操作(第17章)
包: golang.org/x/sync/errgroup
方法: WithContext(ctx), Go(f), TryGo(f), SetLimit(n), Wait()
内部: WaitGroup + 信号量(channel) + Once + Context
场景: 一组 goroutine 任一失败则全部取消
关键知识:
- 第一个非 nil error 取消 Context,Wait 返回该 error
- SetLimit 控制并发 goroutine 数量
- 零值也合法(但没有 Context 取消能力)
- 需要收集所有结果时,用额外 slice 存储
Rate Limiter — 限流(第18章)
包: golang.org/x/time/rate
方法: NewLimiter(r, b), Allow/Reserve/Wait (及 N 版本)
类型: 令牌桶,容量 b (初始满桶),速率 r
令牌桶 vs 漏桶: 令牌桶允许突发(burst),漏桶严格平滑输出。对资源利用率要求高→令牌桶,对处理速度要求严格→漏桶。
常用第三方并发库
| 库 | 类型 | 亮点 |
|---|---|---|
marusama/cyclicbarrier | 循环屏障 | 可重用,参与者互相等待,多轮使用 |
go-pkgz/syncs | 分组操作 | SizedGroup/ErrSizedGroup,控制并发数 |
vardius/gollback | 分组操作 | All/Race/Retry,直接返回结果和错误 |
AaronJan/Hunch | 分组操作 | All/Take/Last/Waterfall 多种编排 |
mdlayher/schedgroup | 定时任务组 | heap 排序避免大量 timer |
juju/ratelimit | 令牌桶 | quantum 每次生成多个令牌 |
uber-go/ratelimit | 漏桶 | 极简 API (Take),WithSlack 支持突发 |
go-redis/redis_rate | 分布式限流 | Redis + Lua, PerSecond/PerMinute |
sony/gobreaker | 断路器 | Closed/Open/Half-Open 三态 |
sourcegraph/conc | 结构化并发 | panic 保护,更简洁的 WaitGroup 封装 |
panjf2000/ants | Worker Pool | 高性能 goroutine 池 |
cespare/percpu | Per-CPU | 无锁高性能计数器 |
valyala/bytebufferpool | Buffer Pool | calibrate 智能调整池大小 |
cenk/backoff | 重试 | 指数退避 |
并发模式速查(第20章)
| 模式 | 场景 | Go 中的体现 |
|---|---|---|
| Half-Async/Half-Sync | 网络服务 | Go 网络库、RPC Client |
| Active Object | 方法异步执行 | channel 解耦调用和执行 |
| Circuit Breaker | 故障保护 | sony/gobreaker |
| Deadline/Timeout | 超时控制 | Context + Timer |
| Balking | 防重复执行 | atomic CAS 检查 |
| Double-Checked Locking | 延迟初始化 | sync.Once 的实现 |
| Guarded Suspension | 条件等待 | Cond/Channel 保护式挂起 |
| Nuclear Reaction | 数据合并/分解 | 并发快排、多级爬虫 |
| Scheduler | 任务调度 | GMP 调度器 |
| Reactor | I/O 事件处理 | Go net 包 |
| Proactor | 异步 I/O | xtaci/gaio |
| Per-CPU | 无锁高性能 | sync.Pool, Timer |
分布式同步原语(第19章)
基于 etcd 实现 (go.etcd.io/etcd/client/v3):
| 原语 | 说明 |
|---|---|
| Election | Leader 选举 (Campaign/Proclaim/Resign/Observe) |
| Locker | 简单分布式锁 (sync.Locker 接口) |
| Mutex | 带 TTL 分布式锁 (崩溃后自动释放) |
| RWMutex | 分布式读写锁 |
| Queue/PriorityQueue | 多读多写分布式队列 |
| Barrier/DoubleBarrier | 分布式屏障(一次性/两阶段) |
| STM | 软件事务内存 (CAS 原子操作) |
关键知识:
- 持有锁的节点崩溃后,Mutex 在 TTL(默认 60s)后自动释放
- DoubleBarrier: Enter 等 count 个节点进入,Leave 等 count 个节点离开
- 优先级队列:数值越小越优先出队
经典并发问题(第21章)
| 问题 | 核心挑战 | 解法 |
|---|---|---|
| 哲学家就餐 | 死锁的四个必要条件 | 限制人数/奇偶编号/资源分级/服务生 |
| 理发师问题 | 并发队列(多写单读) | Cond 或 Channel Semaphore |
| 水工厂问题 | 三线程协同+循环 | CyclicBarrier + Semaphore |
| Fizz Buzz | 四 goroutine 交替输出 | channel 链串行化 |
参考資料
より詳細な情報が必要な場合は、以下を読み込んでください。
references/traps.md— 各 sync プリミティブでよくある落とし穴と誤りのパターンreferences/patterns.md— 並行処理の設計パターンとベストプラクティスreferences/version-changes.md— Go 1.20→1.27 における sync パッケージの変更点の詳細references/deadlock.md— デッドロックの診断と goroutine リークの検出references/memory-model.md— Go メモリモデルの要点references/extended-primitives.md— 公式の拡張並行処理プリミティブ(セマフォ/SingleFlight/ErrGroup/レート制限)references/third-party-libs.md— サードパーティの並行処理ライブラリ(CyclicBarrier/gollback/Hunch/サーキットブレーカー/WorkerPool など)references/concurrency-patterns.md— 並行処理パターン(Half-Async/Sync、Active Object、Reactor など)references/distributed-primitives.md— 分散同期プリミティブ(etcd ベースの選挙/ロック/キュー/バリア/STM)references/classic-problems.md— 古典的な並行処理の問題(哲学者/理髪師/水工場/FizzBuzz)
クイックチェックリスト
Go の並行処理コードをレビューする際は、次の項目を確認してください。
-
-raceを使ってテストしましたか? -
go vetを使って検査しましたか(ロックのコピー検出)? - ロックの保持時間は可能な限り短くなっていますか?
- ロックの再入が発生する可能性はありますか?
- すべての経路で Unlock が呼び出されていますか?(panic/error の経路も含む)
- ロックの取得順序は一貫していますか?
- WaitGroup.Add は Wait より前に呼び出されていますか?
- WaitGroup.Done は正しく対になっていますか?
- Once.Do の f が panic を起こし、初期化されないリスクはありますか?
- goroutine 内で
t.Fatalを使用していませんか? - map の並行読み書きは保護されていますか?
- sync プリミティブをコピーしていませんか?
- Context は第 1 引数になっていますか?
- Channel を閉じる側は正しいですか?(送信側が閉じる)
この Skill は《Go 並行プログラミング実践》に基づいて作成されており、Go 1.20 ~ 1.27 の sync 関連知識を網羅しています。