False Sharing:不同线程改不同变量,为什么也会互相拖慢?
TL;DR
False Sharing 不是线程争用同一个变量,而是不同核心频繁修改同一缓存行中的独立数据。本文解释缓存一致性为什么会制造额外流量,以及如何先诊断再优化。
False Sharing 不是线程争用同一个变量,而是不同核心频繁修改同一缓存行中的独立数据。本文解释缓存一致性为什么会制造额外流量,以及如何先诊断再优化。

想象一张很长的共享办公桌:两位同事各自只动自己的文件,却因为文件恰好挤在同一个抽屉里,每次改动都让另一人的工作区失效。多线程程序里也有类似现象,名字叫 False Sharing,通常译作“伪共享”。它不是线程真的读写同一个变量,而是不同核心修改的变量落在同一条缓存行里。
现代处理器不会为每个字节单独管理缓存一致性,而是以一块固定大小的数据为单位搬运和协调缓存,这块单位叫缓存行。核心 A 修改其中一个字段时,硬件一致性协议可能需要让其他核心上的对应缓存行失效。即便核心 B 改的是同一行里的另一个、逻辑上完全独立的字段,它持有的副本也可能被作废;下一次访问就得重新取得缓存行。若两个核心反复写各自的计数器,缓存行便像乒乓球一样在核心之间来回争抢。
因此,程序表面上“没有共享变量”,仍可能在硬件层面共享缓存行。典型诱因是紧凑排列的计数器数组、相邻对象中的高频写字段,以及多个工作线程更新彼此相邻的统计值。读取少、写入频繁、线程又分布在不同核心时,这种一致性流量尤其值得排查。它会让并行程序扩展到更多核心后收益变小,甚至比少开几个线程还慢。
排查时不要凭“看起来相邻”就立刻改结构。先用基准测试确认热点,再借助 Linux 的 perf c2c 等工具观察缓存行竞争,结合结构布局工具检查哪些字段同处一行。诊断证据比猜测重要,因为真正的热点也可能是锁竞争、内存带宽或调度开销。修复之后要重新测量,并关注整个工作负载,而不是只看单个微基准。
常见缓解方法是让不同线程频繁写入的数据彼此隔开,例如每线程本地计数、分批汇总、调整结构布局,必要时按缓存行对齐或填充。代价是内存占用上升,数据布局更复杂,也可能损伤缓存局部性。若数据更新很少,或者这些字段总由同一个核心处理,填充反而只是在浪费内存。优化的核心不是“把所有东西都隔开”,而是先定位哪些写入确实造成跨核心争用。
这个术语提醒我们:并发冲突不只发生在编程语言能看见的变量和锁上,硬件管理缓存的粒度也会影响性能。它常见于高性能服务、数据库、游戏引擎和科学计算代码,也说明“逻辑独立”不等于“物理上互不干扰”。



