并发性能优化
问题
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 池限制并发数。