直接回答:CPU 缓存一致性以缓存行(通常 64B)为粒度:两个核分别写两个无关联变量,但变量落在同一缓存行,每次写都使对方的行副本失效,总线狂刷——逻辑上无共享,物理上互相踩踏,性能可差一个数量级。Go 高发场景:结构体里各 goroutine 独占的计数器字段挨着放、切片元素被多核并行更新。
展开解析:识别手段:pprof 看不出(CPU 都“在干活”),靠 perf 的 c2c(cache-to-cache 转移)事件或 perf stat 的 cache-misses 异常;代码层面看并行扩展性——核数翻倍性能不涨反降,锁又没有,就要怀疑伪共享。消除方法:填充隔离——结构体内高频写字段之间插入 [64]byte 或按 cache line 对齐(Go 无对齐指令,用数组填充是惯例,runtime 源码里大量 cpu.CacheLinePad);分片——把共享计数器改成每核一片(GOMAXPROCS 大小的数组,按 P 索引),汇总时遍历,这是 sharded counter 标准做法,Go 的 runtime mstats 与此同理;布局调整——把只读字段与热写字段分开存放。切片并行更新的修正:步长按 64B 对齐分块,或每核写自己的结果槽再归并。验证:消除后用 benchstat 看多核 -cpu=N 的吞吐曲线恢复线性。注意反向代价:填充浪费内存、分片增加汇总成本,小对象低频写不值得——伪共享优化只针对 profile 证实的热路径。多核 NUMA 机器上问题放大,跨 socket 的行失效更贵。
追问方向:为什么锁竞争消失后伪共享才显现?Go 为什么没有 align 指令?
(约 470 字)