伪共享是多线程中因不同变量同处一缓存行引发的性能瓶颈:一核写导致整行失效,他核读写需重加载,造成缓存行颠簸;根源在于CPU以64字节缓存行为单位管理内存,且仅并发写才触发问题。

伪共享是多线程程序中一种隐蔽的性能瓶颈:多个线程频繁修改位于同一CPU缓存行(通常是64字节)但逻辑上无关的变量,导致缓存行在核心间反复无效化和同步,严重拖慢执行速度。
为什么会出现伪共享
CPU以缓存行为单位加载和写回内存。即使两个变量在代码中完全独立、分属不同线程,只要它们在内存中地址相近(比如相邻成员变量、数组相邻元素),就可能落入同一缓存行。当一个线程修改其中某个变量时,整个缓存行被标记为“已修改”,其他核心上该行副本立即失效;另一线程读/写同行内另一变量时,必须重新从内存或其它核心拉取整行——这叫“缓存行颠簸”(cache line bouncing)。
典型伪共享场景
结构体成员挤在一起
多个线程各自更新不同对象的某个字段(如 counter),但这些对象是连续分配的数组,且 counter 在结构体开头或未对齐:
struct alignas(64) PaddedCounter { // 对齐到64字节边界
std::atomic value{0};
char _pad[64 - sizeof(std::atomic)]; // 填充至一整行
};
std::vector counters(4); // 每个counter独占一行
数组元素被不同线程轮着改
比如4个线程分别累加 arr[0]、arr[1]、arr[2]、arr[3],而 sizeof(int) == 4,64字节能塞16个int——前4个很可能同属一行。
避免伪共享的实用方法
手动填充(Padding)
用 alignas(64) 强制结构体按缓存行对齐,并在热点变量后补足空间,确保它独占一行。注意:仅对真正被多线程并发写的变量做填充,别盲目加。
重排数据布局
把会被同一线程访问的变量放一起(提高局部性),把被不同线程写的变量尽量拉开距离。例如用结构体数组(AOS)改成数组结构体(SOA):把所有线程的 x 存一起、所有 y 存一起,再按线程号索引,减少跨行访问。
使用线程局部存储(TLS)+ 合并汇总
每个线程先写自己的本地计数器(如 thread_local int local_count),最后单线程合并到全局变量。避免任何跨线程写竞争。
确认是否真有伪共享
别靠猜测优化。用 perf(Linux)看 cache-misses、LLC-load-misses 是否异常高;或用 VTune、perf annotate 定位热点指令的内存访问模式。有时候看似伪共享,其实是锁争用或内存带宽不足。
基本上就这些。伪共享不复杂但容易忽略,关键在“写”——只读不会触发无效化,只有并发写才危险;排查时优先看数据布局和缓存行对齐,而不是一上来就改算法。










