内存模型,加载获取语义实际上是如何工作的?

Lon*_*gLT 3 c++ memory-model stdatomic instruction-reordering

来自关于内存重新排序的非常好的论文文章

Q1:我知道缓存一致性、存储缓冲区和失效队列是内存重新排序的根本原因吗?

存储释放是很好理解的,必须等待所有加载和存储完成才能将标志设置为 true。

关于加载获取,原子加载的典型用途是等待标志。假设我们有 2 个线程:

int x = 0;
std::atomic<bool> ready_flag = false;
Run Code Online (Sandbox Code Playgroud)
// thread-1
if(ready_flag.load(std::memory_order_relaxed))
{
    // (1)
    // load x here
}
// (2)
// load x here
Run Code Online (Sandbox Code Playgroud)
// thread-2
x = 100;
ready_flag.store(true, std::memory_order_release);
Run Code Online (Sandbox Code Playgroud)

编辑:在线程 1 中,它应该是一个 while 循环,但我复制了上面文章中的逻辑。因此,假设内存重新排序及时发生。

Q2 : 因为(1)和(2)取决于if条件,CPU必须等待ready_flag,这是否意味着write-release就足够了?在这种情况下,内存重新排序是如何发生的?

Q3:显然我们有load-acquire,所以我猜 mem-reorder 是可能的,那么我们应该把栅栏放在哪里,(1)还是(2)?

Nic*_*las 5

访问原子变量不是互斥操作;它只是原子地访问存储的值,任何 CPU 操作都没有机会中断访问,这样访问该值时就不会发生数据竞争(它还可以对其他访问发出障碍,这就是内存的顺序)提供)。但仅此而已;它不会等待任何特定值出现在原子变量中。

因此,您的if语句将读取当时恰好存在的任何值。如果您想保护对它的访问,x直到另一个语句写入它并发出原子信号,您必须:

  1. 在原子标志返回值之前不允许读取任何代码。仅仅测试一次该值并不能做到这一点;您必须循环重复访问,直到它为止。任何其他读取尝试都会导致数据争用,因此是未定义的行为。xtruetruex

  2. 每当访问该标志时,您必须以某种方式执行此操作,以告诉系统由线程设置该标志写入的值应该对看到设置值的后续操作可见。这需要一个正确的内存顺序,至少必须是memory_order_acquire.

    从技术角度来说,从标志本身读取并不一定要进行获取。从标志中读取正确的值后,您可以执行获取操作。但是您需要在读取之前进行等效的获取操作x

  3. 写入语句必须使用释放内存顺序设置标志,该顺序必须至少与 一样强大memory_order_release