十张图详解CPU缓存一致性( 二 )


这样的好处是 , 如果我们大量的操作都能够命中缓存 , 那么大部分时间里 CPU 都不需要读写内存 , 自然性能相比写直达会高很多 。
缓存一致性问题现在 CPU 都是多核的 , 由于 L1/L2 Cache 是多个核心各自独有的 , 那么会带来多核心的缓存一致性(Cache Coherence) 的问题 , 如果不能保证缓存一致性的问题 , 就可能造成结果错误 。
那缓存一致性的问题具体是怎么发生的呢?我们以一个含有两个核心的 CPU 作为例子看一看 。
假设 A 号核心和 B 号核心同时运行两个线程 , 都操作共同的变量 i(初始值为 0 ) 。
十张图详解CPU缓存一致性文章插图
这时如果 A 号核心执行了 i++ 语句的时候 , 为了考虑性能 , 使用了我们前面所说的写回策略 , 先把值为 1 的执行结果写入到 L1/L2 Cache 中 , 然后把 L1/L2 Cache 中对应的 Block 标记为脏的 , 这个时候数据其实没有被同步到内存中的 , 因为写回策略 , 只有在 A 号核心中的这个 Cache Block 要被替换的时候 , 数据才会写入到内存里 。
如果这时旁边的 B 号核心尝试从内存读取 i 变量的值 , 则读到的将会是错误的值 , 因为刚才 A 号核心更新 i 值还没写入到内存中 , 内存中的值还依然是 0 。 这个就是所谓的缓存一致性问题 , A 号核心和 B 号核心的缓存 , 在这个时候是不一致 , 从而会导致执行结果的错误 。
十张图详解CPU缓存一致性文章插图
那么 , 要解决这一问题 , 就需要一种机制 , 来同步两个不同核心里面的缓存数据 。 要实现的这个机制的话 , 要保证做到下面这 2 点:

  • 第一点 , 某个 CPU 核心里的 Cache 数据更新时 , 必须要传播到其他核心的 Cache , 这个称为写传播(Wreite Propagation);
  • 第二点 , 某个 CPU 核心里对数据的操作顺序 , 必须在其他核心看起来顺序是一样的 , 这个称为事务的串形化(Transaction Serialization) 。
第一点写传播很容易就理解 , 当某个核心在 Cache 更新了数据 , 就需要同步到其他核心的 Cache 里 。
而对于第二点事务的串形化 , 我们举个例子来理解它 。
假设我们有一个含有 4 个核心的 CPU , 这 4 个核心都操作共同的变量 i(初始值为 0 ) 。 A 号核心先把 i 值变为 100 , 而此时同一时间 , B 号核心先把 i 值变为 200 , 这里两个修改 , 都会「传播」到 C 和 D 号核心 。
十张图详解CPU缓存一致性文章插图
那么问题就来了 , C 号核心先收到了 A 号核心更新数据的事件 , 再收到 B 号核心更新数据的事件 , 因此 C 号核心看到的变量 i 是先变成 100 , 后变成 200 。
而如果 D 号核心收到的事件是反过来的 , 则 D 号核心看到的是变量 i 先变成 200 , 再变成 100 , 虽然是做到了写传播 , 但是各个 Cache 里面的数据还是不一致的 。
所以 , 我们要保证 C 号核心和 D 号核心都能看到相同顺序的数据变化 , 比如变量 i 都是先变成 100 , 再变成 200 , 这样的过程就是事务的串形化 。
要实现事务串形化 , 要做到 2 点:
  • CPU 核心对于 Cache 中数据的操作 , 需要同步给其他 CPU 核心;
  • 要引入「锁」的概念 , 如果两个 CPU 核心里有相同数据的 Cache , 那么对于这个 Cache 数据的更新 , 只有拿到了「锁」 , 才能进行对应的数据更新 。
那接下来我们看看 , 写传播和事务串形化具体是用什么技术实现的 。
总线嗅探写传播的原则就是当某个 CPU 核心更新了 Cache 中的数据 , 要把该事件广播通知到其他核心 。 最常见实现的方式是总线嗅探(Bus Snooping) 。
我还是以前面的 i 变量例子来说明总线嗅探的工作机制 , 当 A 号 CPU 核心修改了 L1 Cache 中 i 变量的值 , 通过总线把这个事件广播通知给其他所有的核心 , 然后每个 CPU 核心都会监听总线上的广播事件 , 并检查是否有相同的数据在自己的 L1 Cache 里面 , 如果 B 号 CPU 核心的 L1 Cache 中有该数据 , 那么也需要把该数据更新到自己的 L1 Cache 。
可以发现 , 总线嗅探方法很简单 ,CPU 需要每时每刻监听总线上的一切活动 , 但是不管别的核心的 Cache 是否缓存相同的数据 , 都需要发出一个广播事件 , 这无疑会加重总线的负载 。
另外 , 总线嗅探只是保证了某个 CPU 核心的 Cache 更新数据这个事件能被其他 CPU 核心知道 , 但是并不能保证事务串形化 。