伪共享是因无关变量同处一缓存行致频繁同步,填充通过间隔64字节使变量独占缓存行;Go用[56]byte、C++用alignas、Java用@Contended实现;替代方案包括拆分对象、数组索引和TLS。

伪共享不是代码写错了,而是变量“站得太近”惹的祸——多个线程高频修改逻辑无关的变量,却因它们落在同一缓存行(通常是64字节)里,被迫频繁同步缓存,拖慢整体性能。
为什么填充能解决问题
CPU以缓存行为单位加载和刷新数据。只要两个变量在内存中地址差小于64字节,就极可能被装进同一个缓存行。当核心1改变量A、核心2改变量B,哪怕A和B完全不相干,MESI协议也会让对方缓存行失效,触发重加载。填充的本质,就是人为拉开变量间距,确保各自独占缓存行。
- 64字节是主流缓存行大小,x86-64和ARM64平台普遍适用
- 填充不是越多越好,目标是让关键字段之间至少间隔64字节
- 结构体起始地址对齐也很重要,否则填充可能失效
怎么填才有效:结构体填充实操要点
以Go或C++为例,高频计数器常是伪共享重灾区。直接在变量间插入无意义字段是最直观方式:
- Go中可用[56]byte填充:前面变量占8字节(int64),加上56字节填充,正好跨满64字节边界,使下一个变量从新缓存行开始
- C++推荐用alignas(std::hardware_destructive_interference_size),该常量在C++17中定义,自动适配当前平台缓存行大小
- Java可借助@Contended注解(需开启-XX:-RestrictContended),JVM自动完成填充,更安全省心
比填充更轻量的替代方案
填充虽有效,但会增加对象体积、影响GC压力或缓存局部性。某些场景下,这些方法更合适:
- 把高频写入字段拆到独立小对象中,天然隔离缓存行
- 用数组+线程ID索引代替共享结构体,如localSum[tid]++,每个线程操作不同数组元素,物理地址天然分散
- 启用线程本地存储(TLS),彻底避免跨核竞争,适合中间结果暂存
验证有没有真正解决
别只看吞吐量提升,要确认底层缓存行为是否改善:
- Linux下用perf stat -e cache-misses,cache-references观察缓存未命中率是否显著下降
- Java可用Async-Profiler采集L1-dcache-load-misses事件,对比填充前后的火焰图热点分布
- Go程序配合pprof的runtime/metrics指标,关注/cpu/classes/gc/assist/mark等是否异常升高——伪共享有时会间接推高GC负担

















