hashmap 和 sync.Map 皆为 unscalable:并发执行 hashmap 的插入操作,因为锁的存在,使用越多cpu,平均操作耗时越长。用 sync.Map 比用 hashmap+锁 的平均操作耗时更长。
一、数据竞争
原因:多个 goroutine 同时接触一个变量,行为不可预知
认定条件:两个及以上 goroutine 同时接触一个变量,其中至少一个 goroutine 为写操作
检测方案:go run -race 或者 go test -race
二、锁的最佳实践
- 减少持有时间:使用 defer 释放锁的时候注意不要增加临界区(尽量用完了就尽快解锁,而defer会在函数最后才被执行,虽然不会忘记解锁,但是加大了持有时间)
- 优化锁的粒度:空间换时间。map 分段锁,比如一个数组长度为x,可以创建一个长度为x的 lock 数组分别对每个位置元素加锁,利用下标控制对应的锁。
- 读写分离:尽量使用读写锁 RWMutex,不管读多写少或者读少写多的场景,相比于 sync.Mutex 仍然会有不少的性能提升;sync.Map 相比于 RWMutex 在 cpu 核数增加时性能更稳定,也是推荐使用。
- 使用原子操作(Lock Free):使用 Go 提供的 atomic.Value。不触发调度,不阻塞执行流。相当于没有命中缓存的访存指令。