Go 并发原语深入:atomic、Mutext、RWMutex 性能对比
mutex 在 Go 项目里无处不在。但很多人不知道何时用 mutex、atomic、还是 sync.Map。本文 perf 三剑客对比并给你选型建议。
一、atomic:最简 + 最快
import"sync/atomic"varcounterint64atomic.AddInt64(&counter,1)atomic.LoadInt64(&counter)二、Mutex:临界区保护
varmu sync.Mutex mu.Lock()defermu.Unlock()counter++三、RWMutex:读多写少
varmu sync.RWMutex mu.RLock()val:=m[key]mu.RUnlock()四、sync.Map:特殊场景
varm sync.Map m.Store("foo",1)v,ok:=m.Load("foo")五、性能对比
BenchmarkAtomic-8 5000000 120 ns/op 0 B/op 0 allocs/op BenchmarkMutex-8 500000 1800 ns/op 0 B/op 0 allocs/op BenchmarkRWMutex-R-8 500000 1500 ns/op 0 B/op 0 allocs/op BenchmarkRWMutex-W-8 500000 2000 ns/op 0 B/op 0 allocs/op BenchmarkSyncMap-8 300000 4200 ns/op 480 B/op 6 allocs/opatomic 远胜 mutex。
六、原则
- 单标量变量:atomic
- 临界区简单:atomic + 复合自旋
- 结构体复合修改:mutex
- 读多写少:RWMutex
- key 集合大,分片明显:sync.Map
七、自旋锁
func(l*Lock)Lock(){for!atomic.CompareAndSwapInt32(&l.flag,0,1){runtime.Gosched()}}func(l*Lock)Unlock(){atomic.StoreInt32(&l.flag,0)}适合临界区极小且极少阻塞场景。
八、TryLock
mu.TryLock()// busy?, skip九、Writer Starvation
当写入者高频,所有 readers 都被饿死。mutex 已内置饥饿机制,Go 1.9+ 改进。
十、踩坑清单
- Mutex 不可复制:传值会惊
- Mutex.Recursive:不支持,必须互斥调用
- defer Unlock:低频;高频用直接 Unlock
十一、企业实战
订单并发场景:
typeOrderCachestruct{mu sync.RWMutex datamap[int64]Order}func(c*OrderCache)Get(idint64)(Order,bool){c.mu.RLock();deferc.mu.RUnlock()o,ok:=c.data[id]returno,ok}十二、sync.Map 实战
- key 集合大且稳定
- 写入不太频繁
十三、Race Detector
gotest-race./...CI 必开。
十四、未来
Go 1.24 计划加入 typed atomics:atomic.Int64、atomic.Pointer[T],API 更安全。
十五、总结与展望
mutex / atomic / sync.Map 没有银弹,按场景选择。
未来:Go 标准库同步原语会变得更安全、更易用。
十六、参考文献
- Go standard library sync
- path/to/Licene