分析伪共享的问题现在我们结合保证多核缓存一致的 MESI 协议 , 来说明这一整个的过程 , 如果你还不知道 MESI 协议 , 你可以看我这篇文章「10 张图打开 CPU 缓存一致性的大门」 。
①最开始变量 A 和 B 都还不在 Cache 里面 , 假设 1 号核心绑定了线程 A , 2 号核心绑定了线程 B , 线程 A 只会读写变量 A , 线程 B 只会读写变量 B 。

文章插图
②1 号核心读取变量 A , 由于 CPU 从内存读取数据到 Cache 的单位是 Cache Line , 也正好变量 A 和 变量 B 的数据归属于同一个 Cache Line , 所以 A 和 B 的数据都会被加载到 Cache , 并将此 Cache Line 标记为「独占」状态 。

文章插图
③接着 , 2 号核心开始从内存里读取变量 B , 同样的也是读取 Cache Line 大小的数据到 Cache 中 , 此 Cache Line 中的数据也包含了变量 A 和 变量 B , 此时 1 号和 2 号核心的 Cache Line 状态变为「共享」状态 。

文章插图
④1 号核心需要修改变量 A , 发现此 Cache Line 的状态是「共享」状态 , 所以先需要通过总线发送消息给 2 号核心 , 通知 2 号核心把 Cache 中对应的 Cache Line 标记为「已失效」状态 , 然后 1 号核心对应的 Cache Line 状态变成「已修改」状态 , 并且修改变量 A 。

文章插图
⑤之后 , 2 号核心需要修改变量 B , 此时 2 号核心的 Cache 中对应的 Cache Line 是已失效状态 , 另外由于 1 号核心的 Cache 也有此相同的数据 , 且状态为「已修改」状态 , 所以要先把 1 号核心的 Cache 对应的 Cache Line 写回到内存 , 然后 2 号核心再从内存读取 Cache Line 大小的数据到 Cache 中 , 最后把变量 B 修改到 2 号核心的 Cache 中 , 并将状态标记为「已修改」状态 。

文章插图
所以 , 可以发现如果 1 号和 2 号 CPU 核心这样持续交替的分别修改变量 A 和 B , 就会重复 ④ 和 ⑤ 这两个步骤 , Cache 并没有起到缓存的效果 , 虽然变量 A 和 B 之间其实并没有任何的关系 , 但是因为同时归属于一个 Cache Line , 这个 Cache Line 中的任意数据被修改后 , 都会相互影响 , 从而出现 ④ 和 ⑤ 这两个步骤 。
因此 , 这种因为多个线程同时读写同一个 Cache Line 的不同变量时 , 而导致 CPU Cache 失效的现象称为伪共享(False Sharing) 。
避免伪共享的方法因此 , 对于多个线程共享的热点数据 , 即经常会修改的数据 , 应该避免这些数据刚好在同一个 Cache Line 中 , 否则就会出现为伪共享的问题 。
接下来 , 看看在实际项目中是用什么方式来避免伪共享的问题的 。
在 Linux 内核中存在 __cacheline_aligned_in_smp 宏定义 , 是用于解决伪共享的问题 。

文章插图
从上面的宏定义 , 我们可以看到:
- 如果在多核(MP)系统里 , 该宏定义是 __cacheline_aligned , 也就是 Cache Line 的大小;
- 而如果在单核系统里 , 该宏定义是空的;
举个例子 , 有下面这个结构体:

文章插图
结构体里的两个成员变量 a 和 b 在物理内存地址上是连续的 , 于是它们可能会位于同一个 Cache Line 中 , 如下图:

文章插图
所以 , 为了防止前面提到的 Cache 伪共享问题 , 我们可以使用上面介绍的宏定义 , 将 b 的地址设置为 Cache Line 对齐地址 , 如下:
推荐阅读
- 一口气看完45个寄存器,CPU核心技术大揭秘
- CPU越来越热吗 一文带你看懂怎么选
- 2020年 你到底需要什么样的CPU来满足你的日常使用呢?
- 关于路由器的2.4G以及5G,你不知道的那些事
- CPU处理器|打造下一个ASML?荷兰投资77亿元研发硅光子芯片技术
- CPU针脚密密麻麻,几千根针脚坏了一两个竟然还能点亮?
- 因为没选对CPU,小万元的3080废了!论电脑CPU如何选购
- i3,i5,i7的cpu到底有什么区别?
- JVM常见线上问题 → CPU 100%、内存泄露 问题排查
- 都是芯片 为什么电脑CPU不能用在手机里
