关于 go-concurrency
面向构建和调试并发系统的 Go 并发 skill,介绍核心原语、常见模式、错误处理、竞态检测,以及控制并发任务的实用指导。
主要功能
- 01Goroutine 与 channel
- 02同步原语
- 03工作池与流水线
- 04取消与错误处理
- 05竞态检测和最佳实践
适用场景
- 01在 Go 中实现并行处理
- 02排查竞态条件和 goroutine 泄漏
- 03用工作池控制并发任务
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ geoffjay/ claude-plugins --skill go-concurrency安装后在 Claude Code 中输入/go-concurrency确认已加载。
详细介绍
Go 并发技能
此技能提供 Go 并发原语和模式方面的专业指导,涵盖 goroutine、通道、同步以及构建并发系统的最佳实践。
适用场景
在以下情况下启用此技能:
- 实现并发/并行处理
- 使用 goroutine 和通道
- 使用同步原语(互斥锁、等待组等)
- 调试竞态条件
- 优化并发性能
- 实现工作池或流水线
- 处理 context 取消
Goroutine 基础知识
基本 Goroutine
// Simple goroutine
go func() {
fmt.Println("Hello from goroutine")
}()
// Goroutine with parameters
go func(msg string) {
fmt.Println(msg)
}("Hello")
// Goroutine with closure
message := "Hello"
go func() {
fmt.Println(message) // Captures message
}()
常见陷阱
// ❌ BAD: Loop variable capture
for i := 0; i < 5; i++ {
go func() {
fmt.Println(i) // All goroutines may print 5
}()
}
// ✅ GOOD: Pass as parameter
for i := 0; i < 5; i++ {
go func(n int) {
fmt.Println(n) // Each prints correct value
}(i)
}
// ✅ GOOD: Create local copy
for i := 0; i < 5; i++ {
i := i // Create new variable
go func() {
fmt.Println(i)
}()
}
通道模式
通道类型
// Unbuffered channel (synchronous)
ch := make(chan int)
// Buffered channel (asynchronous up to buffer size)
ch := make(chan int, 10)
// Send-only channel
func send(ch chan<- int) {
ch <- 42
}
// Receive-only channel
func receive(ch <-chan int) {
value := <-ch
}
// Bidirectional channel
ch := make(chan int)
通道操作
// Send
ch <- value
// Receive
value := <-ch
// Receive with ok check
value, ok := <-ch
if !ok {
// Channel closed
}
// Close channel
close(ch)
// Range over channel
for value := range ch {
fmt.Println(value)
}
Select 语句
// Wait for first available operation
select {
case msg1 := <-ch1:
fmt.Println("Received from ch1:", msg1)
case msg2 := <-ch2:
fmt.Println("Received from ch2:", msg2)
case ch3 <- value:
fmt.Println("Sent to ch3")
default:
fmt.Println("No channels ready")
}
// Timeout pattern
select {
case result := <-ch:
return result, nil
case <-time.After(5 * time.Second):
return nil, errors.New("timeout")
}
// Context cancellation
select {
case result := <-ch:
return result, nil
case <-ctx.Done():
return nil, ctx.Err()
}
同步原语
Mutex
type SafeCounter struct {
mu sync.Mutex
count int
}
func (c *SafeCounter) Increment() {
c.mu.Lock()
defer c.mu.Unlock()
c.count++
}
func (c *SafeCounter) Value() int {
c.mu.Lock()
defer c.mu.Unlock()
return c.count
}
RWMutex
type Cache struct {
mu sync.RWMutex
items map[string]interface{}
}
func (c *Cache) Get(key string) (interface{}, bool) {
c.mu.RLock() // Multiple readers allowed
defer c.mu.RUnlock()
value, ok := c.items[key]
return value, ok
}
func (c *Cache) Set(key string, value interface{}) {
c.mu.Lock() // Exclusive write access
defer c.mu.Unlock()
c.items[key] = value
}
WaitGroup
func processItems(items []Item) {
var wg sync.WaitGroup
for _, item := range items {
wg.Add(1)
go func(item Item) {
defer wg.Done()
process(item)
}(item)
}
wg.Wait() // Wait for all goroutines
}
Once
type Database struct {
instance *sql.DB
once sync.Once
}
func (d *Database) GetConnection() *sql.DB {
d.once.Do(func() {
d.instance, _ = sql.Open("postgres", "connection-string")
})
return d.instance
}
并发模式
工作池
type WorkerPool struct {
workerCount int
jobs chan Job
results chan Result
wg sync.WaitGroup
}
type Job struct {
ID int
Data interface{}
}
type Result struct {
JobID int
Value interface{}
Error error
}
func NewWorkerPool(workerCount int) *WorkerPool {
return &WorkerPool{
workerCount: workerCount,
jobs: make(chan Job, 100),
results: make(chan Result, 100),
}
}
func (p *WorkerPool) Start(ctx context.Context) {
for i := 0; i < p.workerCount; i++ {
p.wg.Add(1)
go p.worker(ctx)
}
}
func (p *WorkerPool) worker(ctx context.Context) {
defer p.wg.Done()
for {
select {
case job, ok := <-p.jobs:
if !ok {
return
}
result := processJob(job)
select {
case p.results <- result:
case <-ctx.Done():
return
}
case <-ctx.Done():
return
}
}
}
func (p *WorkerPool) Submit(job Job) {
p.jobs <- job
}
func (p *WorkerPool) Results() <-chan Result {
return p.results
}
func (p *WorkerPool) Close() {
close(p.jobs)
p.wg.Wait()
close(p.results)
}
// Usage
ctx := context.Background()
pool := NewWorkerPool(10)
pool.Start(ctx)
for i := 0; i < 100; i++ {
pool.Submit(Job{ID: i, Data: fmt.Sprintf("job-%d", i)})
}
go func() {
for result := range pool.Results() {
if result.Error != nil {
log.Printf("Job %d failed: %v", result.JobID, result.Error)
} else {
log.Printf("Job %d completed: %v", result.JobID, result.Value)
}
}
}()
pool.Close()
流水线模式
// Generator stage
func generator(ctx context.Context, nums ...int) <-chan int {
out := make(chan int)
go func() {
defer close(out)
for _, n := range nums {
select {
case out <- n:
case <-ctx.Done():
return
}
}
}()
return out
}
// Processing stage
func square(ctx context.Context, in <-chan int) <-chan int {
out := make(chan int)
go func() {
defer close(out)
for n := range in {
select {
case out <- n * n:
case <-ctx.Done():
return
}
}
}()
return out
}
// Another processing stage
func double(ctx context.Context, in <-chan int) <-chan int {
out := make(chan int)
go func() {
defer close(out)
for n := range in {
select {
case out <- n * 2:
case <-ctx.Done():
return
}
}
}()
return out
}
// Usage - compose pipeline
ctx := context.Background()
numbers := generator(ctx, 1, 2, 3, 4, 5)
squared := square(ctx, numbers)
doubled := double(ctx, squared)
for result := range doubled {
fmt.Println(result)
}
扇出/扇入
// Fan-out: distribute work to multiple goroutines
func fanOut(ctx context.Context, input <-chan int, workers int) []<-chan int {
channels := make([]<-chan int, workers)
for i := 0; i < workers; i++ {
channels[i] = worker(ctx, input)
}
return channels
}
func worker(ctx context.Context, input <-chan int) <-chan int {
output := make(chan int)
go func() {
defer close(output)
for n := range input {
select {
case output <- expensiveOperation(n):
case <-ctx.Done():
return
}
}
}()
return output
}
// Fan-in: merge multiple channels into one
func fanIn(ctx context.Context, channels ...<-chan int) <-chan int {
var wg sync.WaitGroup
output := make(chan int)
multiplex := func(ch <-chan int) {
defer wg.Done()
for n := range ch {
select {
case output <- n:
case <-ctx.Done():
return
}
}
}
wg.Add(len(channels))
for _, ch := range channels {
go multiplex(ch)
}
go func() {
wg.Wait()
close(output)
}()
return output
}
// Usage
ctx := context.Background()
input := generator(ctx, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
// Fan-out to 3 workers
workers := fanOut(ctx, input, 3)
// Fan-in results
results := fanIn(ctx, workers...)
for result := range results {
fmt.Println(result)
}
信号量模式
type Semaphore struct {
sem chan struct{}
}
func NewSemaphore(maxConcurrency int) *Semaphore {
return &Semaphore{
sem: make(chan struct{}, maxConcurrency),
}
}
func (s *Semaphore) Acquire() {
s.sem <- struct{}{}
}
func (s *Semaphore) Release() {
<-s.sem
}
// Usage
sem := NewSemaphore(5) // Max 5 concurrent operations
for _, item := range items {
sem.Acquire()
go func(item Item) {
defer sem.Release()
process(item)
}(item)
}
速率限制
// Token bucket rate limiter
type RateLimiter struct {
ticker *time.Ticker
tokens chan struct{}
}
func NewRateLimiter(rate time.Duration, burst int) *RateLimiter {
rl := &RateLimiter{
ticker: time.NewTicker(rate),
tokens: make(chan struct{}, burst),
}
// Fill bucket initially
for i := 0; i < burst; i++ {
rl.tokens <- struct{}{}
}
// Refill tokens
go func() {
for range rl.ticker.C {
select {
case rl.tokens <- struct{}{}:
default:
}
}
}()
return rl
}
func (rl *RateLimiter) Wait(ctx context.Context) error {
select {
case <-rl.tokens:
return nil
case <-ctx.Done():
return ctx.Err()
}
}
func (rl *RateLimiter) Stop() {
rl.ticker.Stop()
}
// Usage
limiter := NewRateLimiter(time.Second/10, 5) // 10 requests per second, burst of 5
defer limiter.Stop()
for _, request := range requests {
if err := limiter.Wait(ctx); err != nil {
log.Printf("Rate limit error: %v", err)
continue
}
processRequest(request)
}
并发代码中的错误处理
errgroup 包
import "golang.org/x/sync/errgroup"
func fetchURLs(ctx context.Context, urls []string) error {
g, ctx := errgroup.WithContext(ctx)
for _, url := range urls {
url := url // Capture for goroutine
g.Go(func() error {
return fetchURL(ctx, url)
})
}
// Wait for all goroutines, return first error
return g.Wait()
}
// With limited concurrency
func fetchURLsLimited(ctx context.Context, urls []string) error {
g, ctx := errgroup.WithContext(ctx)
g.SetLimit(10) // Max 10 concurrent
for _, url := range urls {
url := url
g.Go(func() error {
return fetchURL(ctx, url)
})
}
return g.Wait()
}
最佳实践
- 始终由发送方关闭通道
- 使用 context 进行取消和超时控制
- 避免 goroutine 泄漏——确保它们能够退出
- 使用缓冲通道以避免阻塞
- 对于读操作密集的工作负载,优先使用 sync.RWMutex
- 避免在热点循环中使用 defer
- 使用竞态检测器进行测试:
go test -race - 使用 errgroup 传递错误
- 使用工作池限制并发操作
- 优化前先进行性能分析
竞态条件检测
# Run tests with race detector
go test -race ./...
# Run program with race detector
go run -race main.go
# Build with race detector
go build -race
应避免的常见模式
// ❌ BAD: Unbounded goroutine creation
for _, item := range millionItems {
go process(item) // May create millions of goroutines
}
// ✅ GOOD: Use worker pool
pool := NewWorkerPool(100)
for _, item := range millionItems {
pool.Submit(item)
}
// ❌ BAD: Goroutine leak
func leak() <-chan int {
ch := make(chan int)
go func() {
ch <- expensiveComputation() // If receiver never reads, goroutine leaks
}()
return ch
}
// ✅ GOOD: Use context for cancellation
func noLeak(ctx context.Context) <-chan int {
ch := make(chan int)
go func() {
defer close(ch)
result := expensiveComputation()
select {
case ch <- result:
case <-ctx.Done():
}
}()
return ch
}
资源
更多示例和模式见:
assets/examples/- 完整的并发示例assets/patterns/- 常见并发模式references/- Go 并发资源和论文的链接
评论
支持 Markdown