一、数据竞争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。不触发调度,不阻塞执行流。相当于没有命中缓存的访存指令。atomic.Value 的前世今生
- 不要拷贝mutex(传参时要传指针,Goland 已默认提示)避免死锁,Go 的 sync.Mutex 是不可重入锁,不可以重复 lock。Go Mutex 设计思想atomic.Value 中应存入只读对象,如果需要取出来再写,则需要加上 Mutex 锁住该操作。race detector 发现潜在问题(用于单测,压测,但是会 10 倍 cpu,不要在线上环境开启)
- Mutex 设计原则:效率优先,兼顾公平正常模式:效率高,新来的goroutine直接先抢锁,无需先排队,等待超过1ms,则切换到饥饿模式 。 为什么正常模式效率高:减少调度开销,新来的不用进入队列;可以充分利用缓存饥饿模式:更公平,新来的goroutine也要先排队,完全先来后到。加锁:原子操作 -> 自旋 -> 加入队列唤醒:不是饥饿状态,可能和其他goroutine竞争;是饥饿状态,直接获得锁,更新锁的状态解锁:fast_path;slow_path
- Go 不安全的 stringGo: 关于锁的1234Go中锁的那些姿势



