跳到主要内容

并发性能优化

问题

Go 并发程序如何优化性能?如何减少锁竞争?

答案

减少锁竞争

// ❌ 全局大锁
type Cache struct {
mu sync.RWMutex
data map[string]string
}

// ✅ 分片锁(减少竞争)
type ShardedCache struct {
shards [256]struct {
mu sync.RWMutex
data map[string]string
}
}

func (c *ShardedCache) getShard(key string) int {
h := fnv.New32a()
h.Write([]byte(key))
return int(h.Sum32()) & 255
}

func (c *ShardedCache) Get(key string) (string, bool) {
shard := &c.shards[c.getShard(key)]
shard.mu.RLock()
defer shard.mu.RUnlock()
val, ok := shard.data[key]
return val, ok
}

无锁方案

// atomic 替代 Mutex
var counter atomic.Int64
counter.Add(1)

// sync.Map 替代 RWMutex + map(读多写少)
var m sync.Map
m.Store("key", "value")
val, _ := m.Load("key")

goroutine 池

避免无限创建 goroutine:

// 使用 ants 库
pool, _ := ants.NewPool(1000) // 最多 1000 个 goroutine
defer pool.Release()

for _, task := range tasks {
task := task
pool.Submit(func() {
process(task)
})
}

// 或用信号量控制并发
sem := make(chan struct{}, 100) // 最多 100 并发
for _, task := range tasks {
sem <- struct{}{}
go func(t Task) {
defer func() { <-sem }()
process(t)
}(task)
}

优化策略总结

策略场景
分片锁高并发 map 读写
atomic简单计数器、标志位
sync.Map读多写少
channel生产者-消费者
goroutine 池限制并发数
singleflight缓存击穿防护

常见面试问题

Q1: sync.Map 和加锁 map 怎么选?

答案

  • sync.Map:读多写少、key 稳定(如缓存),利用分离 dirty/read map 实现无锁读
  • 加锁 map:写操作多、需要遍历、需要精确长度

大多数场景 RWMutex + map 更可控。

Q2: goroutine 创建太多的影响?

答案

  • 每个 goroutine 栈起始 2KB 可增长到 1GB
  • 百万 goroutine ≈ 2GB 内存
  • 调度开销增大
  • 下游资源(DB 连接)可能被打垮

解决:goroutine 池限制并发数。

相关链接