内存控制器如何在传播缓存行时保证原子的内存顺序?

问题描述 投票:0回答:1

我目前正在深入研究

std::atomics
和 C++ 内存模型。真正对我的心智模型有帮助的是 CPU 的存储和加载缓冲区的概念,它基本上是一个 fifo 队列,用于必须写入或读取至少存在于英特尔架构中的 L1 缓存的数据。我知道原子操作基本上是给 CPU 的指令,它防止包装类型在编译时或运行时 撕裂 跨障碍重新排序写入或读取指令。为了说明我心智模型中的差距,我很快想到了这个例子:

演示

#include <atomic>
#include <iostream>
#include <thread>

int a;
int b;
int c;
std::atomic<int> x;
int e = 0;

auto thread1() {
    while(1) {
        a = 3;
        b = 5;
        c = 1;
        x.store(10, std::memory_order::release);
        e++;
        std::cout << "stored!" << std::endl;
    }
}

auto thread2() {
    while(1) {
        x.load(std::memory_order::acquire);
        std::cout << b << std::endl;
    }
}


int main() {
    [[maybe_unused]] auto t1 = std::thread(&thread1);
    [[maybe_unused]] auto t2 = std::thread(&thread2);
}

在这里,一个线程写入全局变量 a、b、c、原子变量 x 和普通变量 e(在递增之前读取),而另一个线程从原子变量 x 和普通变量 b 中读取。对于下一部分,假设两个线程实际上都在不同的 CPU 内核上运行。还要记住,这个简单的例子完全忽略了竞争同步,只提供一个静态例子。

现在这是我的心智模型:

正如您所见,存储缓冲区以有序的方式将数据馈送到 L1 缓存中。然后数据通过 L2 和 L3 缓存传播到主内存。没有人知道它什么时候会到达那里,但它会以 64 Kb 的完整缓存行到达(在大多数体系结构上)。现在让我们假设全局变量 a、b、c 碰巧放在与 x 和 e 不同的缓存行上。这引发了我的问题:内存控制器如何知道传播两个缓存行,以便遵守 x 上的原子操作隐含的内存顺序

我的意思是,如果cacheline 1)恰好在CL 2)之前到达主内存,一切都很好,新写入的a、b和c的值在x的存储之前对其他线程可见。但是,如果发生相反的情况怎么办?如果高速缓存行 2) 首先传播,则对 x 和可能的 e 的写入将在对 a、b 和 c 的写入之前可见,这将导致无效的内存排序。必须以某种方式防止这种情况。我想出了一些可能的解决方案:

  1. 内存控制器将始终按照它们在 L1 中更新的相同顺序传播缓存行。由于 CL 2) 在 1) 之后更新,它将在 2) 之前先将 1) 推送到 main 并且满足约束。
  2. 内存控制器以某种方式“知道”缓存行的排序关系,并且基本上记下哪个 CL 首先通过缓存传播。

可能还有其他我现在想不到的解决方案,但我认为理解这个拼图将帮助我完成我的心理理解,达到可接受的细节数量。如果我的理解有某种缺陷,也请纠正我。

c++ concurrency atomic stdatomic memory-model
1个回答
0
投票

如果我理解 cppreference memory order page 只有原子变量和它依赖的变量保证是可见的。您描述的第二种情况似乎是有效的内存排序。

© www.soinside.com 2019 - 2024. All rights reserved.