把原来加锁的 map 换成 sync.Map 之后,并发改同一个结构体字段仍然读到脏数据,竞态检测器照旧报警,只是位置换到了字段赋值那一行,看着像换了个对象没换问题。日志里能对上时间点,这种反而好排。
sync.Map 只保证键值对本身的操作原子,存进去的指针指向的对象完全不受保护,多个协程改同一个结构体字段依旧是数据竞争,锁要加在对象上而不是表上。这一点先确认清楚,后面就不用反复试。
grep -n 'sync.Map' -r . go test -race ./... go tool pprof -http=:8080 mutex.out
「sync.Map 值并发」改完先别急着全量,挑一台机器放半天,真有问题这段时间就能看出来。线上出问题先对时间点,跟发布、备份、任务执行的时刻排一排,能省掉一半功夫。
处理上按这个顺序:存进去的值改成不可变对象或者干脆存值类型;必须修改字段的时候对对象本身再加一层锁;用竞态检测把所有的字段写入位置再复查一遍。
结果对不对很好判断,竞态检测器不再指向字段赋值,压测读到的值前后一致,没有脏数据。
