go-concurrency
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseGo Concurrency Skill
Go并发技能
Operator Context
操作上下文
This skill operates as an operator for Go concurrency workflows, configuring Claude's behavior for correct, leak-free concurrent code. It implements the Domain Intelligence architectural pattern -- encoding Go concurrency idioms, sync primitives, and channel patterns as non-negotiable constraints rather than suggestions.
本技能作为Go并发工作流的操作器,配置Claude的行为以生成正确、无泄漏的并发代码。它实现了领域智能架构模式——将Go并发惯用写法、同步原语和通道模式编码为不可协商的约束而非建议。
Hardcoded Behaviors (Always Apply)
硬编码行为(始终适用)
- CLAUDE.md Compliance: Read and follow repository CLAUDE.md before writing concurrent code
- Over-Engineering Prevention: Use concurrency only when justified by I/O, CPU parallelism, or measured bottleneck. Sequential code is correct by default
- Context First Parameter: All cancellable or I/O operations accept as first parameter
context.Context - No Goroutine Leaks: Every goroutine must have a guaranteed exit path via context, channel close, or explicit shutdown
- Race Detector Required: Run on all concurrent code during development
go test -race - Channel Ownership: Only the sender closes a channel. Never close from receiver side
- Select With Context: Every statement in concurrent code must include a
selectcase<-ctx.Done()
- 遵循CLAUDE.md规范:编写并发代码前阅读并遵循仓库中的CLAUDE.md
- 避免过度设计:仅当I/O、CPU并行性或已测量到瓶颈时才使用并发。顺序代码默认是正确的
- 上下文作为首个参数:所有可取消或I/O操作都接受作为首个参数
context.Context - 无Goroutine泄漏:每个goroutine必须通过上下文、通道关闭或显式关闭拥有明确的退出路径
- 必须使用竞态检测器:开发期间对所有并发代码运行
go test -race - 通道所有权规则:仅发送方可以关闭通道,绝不能从接收方侧关闭
- Select语句需包含上下文:并发代码中的每个语句必须包含
select分支<-ctx.Done()
Default Behaviors (ON unless disabled)
默认行为(启用状态,除非手动禁用)
- errgroup Over WaitGroup: Prefer for goroutine management with error collection
golang.org/x/sync/errgroup - Buffered Channel Sizing: Buffer size matches expected backpressure, not arbitrary large numbers
- Directional Channel Returns: Return (receive-only) from producer functions to prevent caller misuse
<-chan T - Mutex Scope Minimization: Lock only the critical section, use immediately after
defer Unlock()Lock() - Loop Variable Safety: Use Go 1.22+ loop variable semantics; remove legacy shadows in new code
item := item - Graceful Shutdown: Workers and servers implement clean shutdown with drain timeout
- Atomic for Counters: Use /
atomic.Int64for simple shared counters instead of mutexatomic.Value
- 优先使用errgroup而非WaitGroup:对于带错误收集的goroutine管理,优先使用
golang.org/x/sync/errgroup - 缓冲通道大小设置:缓冲区大小匹配预期的背压,而非随意设置大数值
- 返回定向通道:生产者函数返回(仅接收)类型,防止调用方误用
<-chan T - 最小化互斥锁作用域:仅锁定临界区,在后立即使用
Lock()defer Unlock() - 循环变量安全:使用Go 1.22+的循环变量语义;在新代码中移除传统的阴影写法
item := item - 优雅关闭:工作协程和服务器实现带排空超时的优雅关闭
- 原子操作实现计数器:对于简单的共享计数器,使用/
atomic.Int64而非互斥锁atomic.Value
Optional Behaviors (OFF unless enabled)
可选行为(禁用状态,除非手动启用)
- Gopls MCP Analysis: Use gopls MCP tools to trace channel usage and context propagation — for tracing channel flow,
go_symbol_referencesfor understanding goroutine spawn sites,go_file_contextafter concurrent code edits. Fallback:go_diagnosticsCLI or LSP toolgopls references - Container GOMAXPROCS Tuning: Configure GODEBUG flags for container CPU limit overrides
- Performance Profiling: Profile goroutine counts and channel contention under load
- Custom Rate Limiter: Build token-bucket rate limiter instead of using
golang.org/x/time/rate
- Gopls MCP分析:使用gopls MCP工具追踪通道使用和上下文传播——用于追踪通道流,
go_symbol_references用于理解goroutine启动位置,go_file_context用于并发代码编辑后的诊断。备选方案:go_diagnosticsCLI或LSP工具gopls references - 容器GOMAXPROCS调优:配置GODEBUG标志以覆盖容器CPU限制
- 性能分析:在负载下分析goroutine数量和通道竞争情况
- 自定义速率限制器:构建令牌桶速率限制器,而非使用
golang.org/x/time/rate
What This Skill CAN Do
本技能可完成的工作
- Guide implementation of worker pools, fan-out/fan-in, and pipeline patterns
- Apply correct context propagation through concurrent call chains
- Select appropriate sync primitives (Mutex, RWMutex, WaitGroup, Once, atomic)
- Implement rate limiting with context-aware waiting
- Diagnose and fix race conditions, deadlocks, and goroutine leaks
- Structure graceful shutdown for background workers and servers
- 指导实现工作池、扇出/扇入和流水线模式
- 在并发调用链中正确传播上下文
- 选择合适的同步原语(Mutex、RWMutex、WaitGroup、Once、atomic)
- 实现支持上下文感知等待的速率限制
- 诊断并修复竞态条件、死锁和goroutine泄漏
- 为后台工作协程和服务器设计优雅关闭逻辑
What This Skill CANNOT Do
本技能无法完成的工作
- Fix general Go bugs unrelated to concurrency (use systematic-debugging instead)
- Optimize non-concurrent performance (use performance optimization workflows instead)
- Write tests for concurrent code (use go-testing skill instead)
- Handle Go error handling patterns (use go-error-handling skill instead)
- 修复与并发无关的通用Go bug(请改用systematic-debugging技能)
- 优化非并发场景的性能(请改用性能优化工作流)
- 为并发代码编写测试(请改用go-testing技能)
- 处理Go错误处理模式(请改用go-error-handling技能)
Instructions
操作步骤
Step 1: Assess Concurrency Need
步骤1:评估并发需求
Before writing concurrent code, answer these questions:
- Is the work I/O-bound? (network, database, filesystem) -- concurrency likely helps
- Is the work CPU-bound? -- concurrency helps only if parallelizable
- Is there a measured bottleneck? -- if not measured, don't assume
If none apply, write sequential code. Concurrency adds complexity; justify it.
编写并发代码前,请回答以下问题:
- 工作是否为I/O密集型?(网络、数据库、文件系统)——并发通常有帮助
- 工作是否为CPU密集型?——仅当可并行化时并发才有帮助
- 是否存在已测量的瓶颈?——若未测量,请勿假设
如果以上都不满足,请编写顺序代码。并发会增加复杂度,必须有合理理由。
Step 2: Choose the Right Primitive
步骤2:选择合适的原语
| Need | Primitive | When |
|---|---|---|
| Communicate between goroutines | Channel | Data flows from producer to consumer |
| Protect shared state | | Multiple goroutines read/write same data |
| Read-heavy shared state | | Many readers, few writers |
| Wait for goroutines to finish | | Need error collection + context cancel |
| Wait without error collection | | Fire-and-forget goroutines |
| One-time initialization | | Lazy singleton, config loading |
| Simple shared counter | | Increment/read without mutex overhead |
| 需求 | 原语 | 适用场景 |
|---|---|---|
| 在goroutine间通信 | Channel | 数据从生产者流向消费者 |
| 保护共享状态 | | 多个goroutine读写同一数据 |
| 读密集型共享状态 | | 大量读操作,少量写操作 |
| 等待goroutine完成 | | 需要错误收集+上下文取消 |
| 无需错误收集的等待 | | 无需关注结果的goroutine |
| 一次性初始化 | | 懒加载单例、配置加载 |
| 简单共享计数器 | | 无互斥锁开销的递增/读取操作 |
Step 3: Context Propagation
步骤3:上下文传播
Always pass context as first parameter for I/O or cancellable operations.
go
func FetchData(ctx context.Context, id string) (*Data, error) {
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
resultCh := make(chan *Data, 1)
errCh := make(chan error, 1)
go func() {
data, err := slowOperation(id)
if err != nil {
errCh <- err
return
}
resultCh <- data
}()
select {
case data := <-resultCh:
return data, nil
case err := <-errCh:
return nil, fmt.Errorf("fetch failed: %w", err)
case <-ctx.Done():
return nil, fmt.Errorf("fetch cancelled: %w", ctx.Err())
}
}When to use context vs not:
go
// USE context: I/O, cancellable operations, request-scoped values
func FetchUserData(ctx context.Context, userID string) (*User, error) { ... }
// NO context needed: pure computation
func CalculateTotal(prices []float64) float64 { ... }对于I/O或可取消操作,始终将上下文作为首个参数传递。
go
func FetchData(ctx context.Context, id string) (*Data, error) {
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
resultCh := make(chan *Data, 1)
errCh := make(chan error, 1)
go func() {
data, err := slowOperation(id)
if err != nil {
errCh <- err
return
}
resultCh <- data
}()
select {
case data := <-resultCh:
return data, nil
case err := <-errCh:
return nil, fmt.Errorf("fetch failed: %w", err)
case <-ctx.Done():
return nil, fmt.Errorf("fetch cancelled: %w", ctx.Err())
}
}何时使用上下文,何时不使用:
go
// 使用context:I/O操作、可取消操作、请求作用域的值
func FetchUserData(ctx context.Context, userID string) (*User, error) { ... }
// 无需context:纯计算操作
func CalculateTotal(prices []float64) float64 { ... }Step 4: Implement the Pattern
步骤4:实现模式
Sync Primitives
go
// Mutex for state protection
type SafeCounter struct {
mu sync.Mutex
count int
}
func (c *SafeCounter) Increment() {
c.mu.Lock()
defer c.mu.Unlock()
c.count++
}
// RWMutex for read-heavy workloads
type Cache struct {
mu sync.RWMutex
items map[string]any
}
func (c *Cache) Get(key string) (any, bool) {
c.mu.RLock()
defer c.mu.RUnlock()
item, ok := c.items[key]
return item, ok
}
func (c *Cache) Set(key string, value any) {
c.mu.Lock()
defer c.mu.Unlock()
c.items[key] = value
}errgroup for concurrent work with error handling (preferred over WaitGroup)
go
import "golang.org/x/sync/errgroup"
func ProcessAll(ctx context.Context, items []Item) error {
g, ctx := errgroup.WithContext(ctx)
for _, item := range items {
g.Go(func() error {
return process(ctx, item) // Go 1.22+: item captured correctly
})
}
return g.Wait()
}sync.Once for one-time initialization
go
type Config struct {
once sync.Once
config *AppConfig
err error
}
func (c *Config) Load() (*AppConfig, error) {
c.once.Do(func() {
c.config, c.err = loadConfigFromFile()
})
return c.config, c.err
}Channel patterns: buffered vs unbuffered
go
// Unbuffered: synchronous, sender blocks until receiver ready
ch := make(chan int)
// Buffered: async up to buffer size
ch := make(chan int, 100)
// Guidelines:
// - Use unbuffered when you need synchronization
// - Use buffered to decouple sender/receiver timing
// - Buffer size should match expected backpressureFor worker pool, fan-out/fan-in, pipeline, rate limiter, and graceful shutdown patterns, see .
references/concurrency-patterns.md同步原语
go
// Mutex用于状态保护
type SafeCounter struct {
mu sync.Mutex
count int
}
func (c *SafeCounter) Increment() {
c.mu.Lock()
defer c.mu.Unlock()
c.count++
}
// RWMutex用于读密集型工作负载
type Cache struct {
mu sync.RWMutex
items map[string]any
}
func (c *Cache) Get(key string) (any, bool) {
c.mu.RLock()
defer c.mu.RUnlock()
item, ok := c.items[key]
return item, ok
}
func (c *Cache) Set(key string, value any) {
c.mu.Lock()
defer c.mu.Unlock()
c.items[key] = value
}errgroup用于带错误处理的并发工作(优先于WaitGroup)
go
import "golang.org/x/sync/errgroup"
func ProcessAll(ctx context.Context, items []Item) error {
g, ctx := errgroup.WithContext(ctx)
for _, item := range items {
g.Go(func() error {
return process(ctx, item) // Go 1.22+: 正确捕获item变量
})
}
return g.Wait()
}sync.Once用于一次性初始化
go
type Config struct {
once sync.Once
config *AppConfig
err error
}
func (c *Config) Load() (*AppConfig, error) {
c.once.Do(func() {
c.config, c.err = loadConfigFromFile()
})
return c.config, c.err
}通道模式:缓冲与非缓冲
go
// 非缓冲:同步,发送方阻塞直到接收方就绪
ch := make(chan int)
// 缓冲:异步,最多容纳指定数量的元素
ch := make(chan int, 100)
// 指南:
// - 需要同步时使用非缓冲通道
// - 使用缓冲通道解耦发送方/接收方的时序
// - 缓冲区大小应匹配预期的背压关于工作池、扇出/扇入、流水线、速率限制器和优雅关闭模式,请参考。
references/concurrency-patterns.mdStep 5: Run Race Detector
步骤5:运行竞态检测器
bash
undefinedbash
undefinedALWAYS run with race detector during development
开发期间务必运行竞态检测器
go test -race -count=1 -v ./...
go test -race -count=1 -v ./...
Run specific test with race detection
对特定测试启用竞态检测
go test -race -run TestConcurrentOperation ./...
undefinedgo test -race -run TestConcurrentOperation ./...
undefinedStep 6: Concurrency Checklist
步骤6:并发检查清单
Before declaring concurrent code complete, verify:
- Context propagation - All I/O operations accept context
- Goroutine exit paths - Every goroutine can terminate
- Channel closure - Channels closed by sender only
- Select with context - All selects include
<-ctx.Done() - Proper synchronization - Shared state protected
- Race detector passes - clean
go test -race - Graceful shutdown - Workers stop cleanly
- No goroutine leaks - All goroutines tracked
在确认并发代码完成前,请验证以下项:
- 上下文传播 - 所有I/O操作都接受上下文
- Goroutine退出路径 - 每个goroutine都能终止
- 通道关闭 - 仅发送方关闭通道
- Select语句包含上下文 - 所有select语句都包含
<-ctx.Done() - 正确同步 - 共享状态已被保护
- 竞态检测器通过 - 无报错
go test -race - 优雅关闭 - 工作协程能干净停止
- 无Goroutine泄漏 - 所有goroutine都被追踪
Error Handling
错误处理
Error: "DATA RACE detected by race detector"
错误:"DATA RACE detected by race detector"
Cause: Multiple goroutines access shared variable without synchronization
Solution:
- Identify the variable from the race detector output (it shows goroutine stacks)
- Protect with for complex state, or
sync.Mutexfor simple countersatomic - If using channels, ensure the variable is only accessed by one goroutine at a time
- Re-run to confirm fix
go test -race
原因:多个goroutine在无同步的情况下访问共享变量
解决方案:
- 从竞态检测器输出中定位变量(会显示goroutine栈信息)
- 对于复杂状态,使用保护;对于简单计数器,使用
sync.Mutex操作atomic - 如果使用通道,确保变量同一时间仅被一个goroutine访问
- 重新运行确认修复
go test -race
Error: "all goroutines are asleep - deadlock!"
错误:"all goroutines are asleep - deadlock!"
Cause: Circular wait on channels or mutexes; no goroutine can make progress
Solution:
- Check for unbuffered channel sends with no receiver ready
- Check for mutex lock ordering inconsistencies
- Ensure channels are closed when done to unblock loops
range - Add buffering to channels where appropriate
原因:通道或互斥锁上的循环等待;没有goroutine能继续执行
解决方案:
- 检查是否存在无接收方就绪的非缓冲通道发送操作
- 检查互斥锁的锁定顺序是否一致
- 确保通道在使用完毕后关闭,以解除循环的阻塞
range - 为通道添加适当的缓冲
Error: "context deadline exceeded" in concurrent operations
错误:并发操作中出现"context deadline exceeded"
Cause: Operations not completing within timeout, or context cancelled upstream
Solution:
- Check if timeout is realistic for the operation
- Verify context is propagated correctly (not using when a parent context exists)
context.Background() - Ensure goroutines check in their select loops
ctx.Done() - Consider increasing timeout or adding per-operation timeouts with
context.WithTimeout
原因:操作未在超时时间内完成,或上游上下文被取消
解决方案:
- 检查超时时间对于操作是否合理
- 验证上下文是否正确传播(当存在父上下文时,不要使用)
context.Background() - 确保goroutine在其select循环中检查
ctx.Done() - 考虑增加超时时间,或使用为每个操作设置单独超时
context.WithTimeout
Anti-Patterns
反模式
Anti-Pattern 1: Goroutine Without Exit Path
反模式1:无退出路径的Goroutine
What it looks like: with no context check or stop channel
Why wrong: Goroutine runs forever, leaking memory. Cannot be cancelled or shut down gracefully.
Do instead: Always include or a stop channel in goroutine loops.
go func() { for { doWork() } }()<-ctx.Done()表现形式: 无上下文检查或停止通道
错误原因:Goroutine会永久运行,导致内存泄漏,无法被取消或优雅关闭
正确做法:Goroutine循环中始终包含或停止通道分支
go func() { for { doWork() } }()<-ctx.Done()Anti-Pattern 2: Unnecessary Concurrency
反模式2:不必要的并发
What it looks like: Spawning goroutines for sequential work that does not benefit from parallelism
Why wrong: Adds complexity (error channels, WaitGroups, race risks) without performance gain. Sequential code is simpler and correct by default.
Do instead: Measure first. Use concurrency only for I/O-bound, CPU-parallel, or proven bottleneck scenarios.
表现形式:为无法从并行中获益的顺序工作启动goroutine
错误原因:无性能提升却增加了复杂度(错误通道、WaitGroups、竞态风险)。顺序代码更简单且默认正确
正确做法:先测量性能。仅在I/O密集型、CPU并行型或已证实的瓶颈场景中使用并发
Anti-Pattern 3: Closing Channel From Receiver Side
反模式3:从接收方侧关闭通道
What it looks like: Consumer goroutine calling on a channel it reads from
Why wrong: Sender may still send, causing panic. Multiple receivers may double-close.
Do instead: Only the sender (producer) closes the channel. Use in the goroutine that writes.
close(ch)defer close(ch)表现形式:消费者goroutine调用关闭其读取的通道
错误原因:发送方可能仍在发送数据,导致panic;多个接收方可能重复关闭通道
正确做法:仅发送方(生产者)可以关闭通道。在写入的goroutine中使用
close(ch)defer close(ch)Anti-Pattern 4: Mutex Lock Without Defer Unlock
反模式4:互斥锁锁定后未使用defer Unlock
What it looks like: followed by complex logic before , with early returns in between
Why wrong: Early returns or panics skip the , causing deadlocks.
Do instead: Always immediately after .
mu.Lock()mu.Unlock()Unlock()defer mu.Unlock()mu.Lock()表现形式:后执行复杂逻辑再调用,中间存在提前返回
错误原因:提前返回或panic会跳过,导致死锁
正确做法:在后立即使用
mu.Lock()mu.Unlock()Unlock()Lock()defer mu.Unlock()Anti-Pattern 5: Ignoring Context in Select
反模式5:Select语句忽略上下文
What it looks like: without a case
Why wrong: Goroutine blocks forever if channel never receives and context is cancelled.
Do instead: Every in concurrent code must include .
select { case msg := <-ch: handle(msg) }<-ctx.Done()selectcase <-ctx.Done(): return表现形式: 不包含分支
错误原因:如果通道从未接收数据且上下文被取消,goroutine会永久阻塞
正确做法:并发代码中的每个语句必须包含
select { case msg := <-ch: handle(msg) }<-ctx.Done()selectcase <-ctx.Done(): returnReferences
参考资料
This skill uses these shared patterns:
- Anti-Rationalization - Prevents shortcut rationalizations
- Verification Checklist - Pre-completion checks
本技能使用以下共享模式:
- Anti-Rationalization - 防止捷径式合理化
- Verification Checklist - 完成前检查
Domain-Specific Anti-Rationalization
领域特定的反合理化
| Rationalization | Why It's Wrong | Required Action |
|---|---|---|
| "No need for context, this is fast" | Fast today, slow tomorrow under load | Pass context to all I/O operations |
| "Race detector is slow, skip it" | Races are silent until production | Run |
| "One goroutine leak won't matter" | Leaks compound; OOM in production | Verify every goroutine has exit path |
| "Sequential is too slow" | Assumption without measurement | Profile first, then add concurrency |
| "Buffer of 1000 should be enough" | Arbitrary buffers hide backpressure bugs | Size buffers to actual throughput |
| 合理化借口 | 错误原因 | 要求操作 |
|---|---|---|
| "不需要上下文,这个操作很快" | 现在快但未来负载下可能变慢 | 为所有I/O操作传递上下文 |
| "竞态检测器太慢,跳过它" | 竞态条件在生产环境才会显现 | 每次都运行 |
| "一个Goroutine泄漏没关系" | 泄漏会累积,最终导致OOM | 验证每个goroutine都有退出路径 |
| "顺序代码太慢了" | 无测量依据的假设 | 先分析性能,再添加并发 |
| "缓冲区设为1000应该足够" | 随意设置的缓冲区会隐藏背压bug | 缓冲区大小匹配实际吞吐量 |
Reference Files
参考文件
- : Worker pool, fan-out/fan-in, pipeline, rate limiter, and graceful shutdown patterns with full code examples
${CLAUDE_SKILL_DIR}/references/concurrency-patterns.md
- :包含工作池、扇出/扇入、流水线、速率限制器和优雅关闭模式的完整代码示例
${CLAUDE_SKILL_DIR}/references/concurrency-patterns.md