在像 x86 这样提供高速缓存一致性的 CPU 上,从实际角度来看,这有什么用处?我知道这个想法是让一个核心上完成的内存更新立即在所有其他核心上可见。这是一个有用的属性。但是,如果不是用汇编语言编写,就不能过分依赖它,因为编译器可以将变量赋值存储在寄存器中,而永远不会将它们写入内存。这意味着仍然必须采取明确的步骤来确保在其他线程中完成的事情在当前线程中是可见的。因此,从实际的角度来看,缓存一致性实现了什么?
6 回答
简而言之,非缓存一致性系统非常难以编程,特别是如果您想保持效率 - 这也是当今大多数 NUMA 系统都具有缓存一致性的主要原因。
如果缓存不连贯,则“显式步骤”将不得不强制执行连贯性 - 显式步骤通常是诸如关键部分/互斥锁之类的东西(例如,C/C++ 中的 volatile 就足够了)。对于诸如互斥锁之类的服务而言,即使不是不可能,也很难仅跟踪所有缓存中发生更改并需要更新的内存-它可能必须更新所有内存,也就是说,如果它甚至可以跟踪哪些内核的缓存中有哪些内存。
据推测,硬件可以在跟踪已更改的内存地址/范围方面做得更好、更有效,并使它们保持同步。
并且,想象一个在核心 1 上运行并被抢占的进程。当它再次被安排时,它被安排在核心 2 上。
如果缓存不一致,这将是非常致命的,否则核心 1 的缓存中可能存在进程数据的残余,而核心 2 的缓存中不存在这些残余。但是,对于以这种方式工作的系统,操作系统必须在调度线程时强制执行缓存一致性——这可能是“更新所有内核之间缓存中的所有内存”操作,或者它可能会通过在 MMU 的帮助下,仅同步已更改的内存页面 - 再次,硬件可能以更细粒度和更有效的方式保持缓存的一致性。
其他作者的出色回应没有涵盖一些细微差别。
首先,考虑 CPU 不是逐字节处理内存,而是处理高速缓存行。一行可能有 64 个字节。现在,如果我在位置 P 分配一块 2 字节的内存,另一个 CPU 在位置 P + 8 分配一块 8 字节的内存,并且 P 和 P + 8 都位于同一缓存行上,请注意没有缓存一致性两个 CPU 不能同时更新 P 和 P + 8 而不破坏彼此的更改!因为每个 CPU 都在缓存行上进行读取-修改-写入,所以它们可能都写出不包含其他 CPU 更改的行的副本!最后一个作家会赢,你对记忆的修改会“消失”!
要记住的另一件事是连贯性和一致性之间的区别。因为即使是 x86 派生的 CPU 也使用存储缓冲区,所以即使编译器决定将值写回,也不能保证已经完成的指令以其他 CPU 可以看到这些修改的方式修改了内存记忆(也许是因为volatile
?)。相反,模组可能会坐在商店缓冲区中。几乎所有一般使用的 CPU 都是缓存一致的,但很少有 CPU 具有像 x86 一样宽容的一致性模型。例如,查看 http://www.cs.nmsu.edu/~pfeiffer/classes/573/notes/consistency.html以了解有关此主题的更多信息。
希望这会有所帮助,顺便说一句,我在 Corensic 工作,这家公司正在构建一个您可能想要查看的并发调试器。当关于并发性、连贯性和一致性的假设被证明是没有根据的时,它有助于解决问题:)
想象一下你这样做:
lock(); //some synchronization primitive e.g. a semaphore/mutex
globalint = somevalue;
unlock();
如果没有缓存一致性,那么最后一个unlock()
必须确保globalint
现在随处可见,有了缓存一致性,您所需要做的就是将其写入内存并让硬件发挥作用。软件解决方案将跟踪哪些内存存在于哪些缓存中,哪些内核上,并以某种方式确保它们原子同步。
如果您能找到一个软件解决方案来跟踪缓存中存在的所有需要保持同步的内存块,那么您将赢得奖项,这比当前的硬件解决方案更有效。
它不需要锁定。如果需要,锁定代码将包括缓存刷新。主要需要保证不同处理器对同一缓存行中不同变量的并发更新不丢失。
当您处理多个线程并从多个线程访问同一个变量时,缓存一致性变得非常重要。在这种特殊情况下,您必须确保所有处理器/内核在同时访问变量时确实看到相同的值,否则您将有非常不确定的行为。
缓存一致性是在硬件中实现的,因为程序员不必担心确保所有线程在多核/多处理器环境中运行时都能看到内存位置的最新值。高速缓存一致性提供了一种抽象,即所有核心/处理器都在单个统一高速缓存上运行,尽管每个核心/处理器都有自己的单独高速缓存。
它还确保旧的多线程代码在新处理器模型/多处理器系统上按原样工作,而无需进行任何代码更改以确保数据一致性。