内存关卡/栅栏 ( Memory Barriers / Fences ) – 译

翻译自:Martin ThompsonMemory Barriers/Fences

在这篇文章里,我将讨论并发编程里最基础的技术–以内存关卡或栅栏著称,那让进程内的内存状态对其他进程可见。

CPU 使用了很多技术去尝试和适应这样的事实:CPU 执行单元的性能已远远超出主内存性能。在我的“Writing Combining”文章,我只是谈及其中一种技术。CPU 使用的用来隐藏内存延迟的最普通技术是管线化指令,然后付出巨大努力和资源去尝试重排序这些管线来最小化缓存不命中的有关拖延。

当一个程序执行的时候,它不在乎,如果重排序后的指令提供了一样的最终结果。例如,在一个循环内,如果循环内没有操作使用循环计算器,循环计数器什么时候更新是不在乎的。编译器和 CPU 自由地重排序指令来最大化地利用 CPU,直到下一次迭代即将开始时才更新它(循环计数器)。也可能,在一个循环的执行过程中,这个变量可能存储在一个寄存器里,永远不会推到缓存或主内存,因此,它对其它 CPU 永远不可见。

CPU 核包含多个执行单元。例如,一个现代的Intel CPU 包含6个执行单元,可以做一组数学,条件逻辑和内存操作的组合。每个执行单元可以做这些任务的组合。这些执行单元并行地操作,允许指令并行地执行。如果从其它 CPU 来观察,这引入了程序顺序的另一层不确定性。

最终,当缓存不命中发生时,现代 CPU 可以根据内存加载的结果做一个假设,然后基于这个假设继续执行直至实际数据的加载完成。

提供“程序顺序”保留了 CPU 和编译器自由地做它们认为可以提升性能的事情。

cpu-memory-cache-system

加载(load)和存储(store)到缓存和主内存是被缓冲和重排序的,使用加载(load),存储(store),和写组合(writing-combining)缓存。这些缓存是关联的队列,允许快速查找。这种查找是必须的,当一个稍后的加载需要读取一个之前存储的、还没有到达缓存的值时。上图描绘了现代多核 CPU 的简化视图。它显示了执行单元如何使用本地寄存器和缓存来管理内存,与缓存子系统来回传送。

在多线程环境下,需要采用一些技术来让程序结果及时可见。我不会在这篇文章里涉及缓存一致性。仅仅假设一旦内存被推到缓存,然后有一个协议消息将发生,以确保所有共享数据的缓存是一致的。这种使内存对处理器核可见的技术被称为内存关卡或栅栏。

内存关卡提供了两种属性。首先,它们保留了外部可见的程序顺序,通过确保所有的、关卡两侧的指令表现出正确的程序顺序,如果从其他CPU观察。第二,它们使内存可见,通过确保数据传播到缓存子系统。

内存关卡是一个复杂的主题。它们在不同的 CPU 架构上的实现是非常不同的。Intel CPU 有一个关联的强内存模型。本篇将以 x86 CPU 为基础讲解。

继续阅读