C11内存栅栏和原子操作

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

我正在研究记忆障碍。 我对以下代码有一些疑问。

//version 1
Thread A:
    *val = 1;
    atomic_thread_fence(memory_order_release);
    atomic_store_explicit(published, 1, memory_order_relaxed);

Thread B:
    if (atomic_load_explicit(published, memory_order_relaxed) == 1) {
            atomic_thread_fence(memory_order_acquire);
            assert(*val == 1); // will never fail
    }

//version 2
/* Thread A */
    *val = 1;
    atomic_thread_fence(memory_order_release);
    *published = 1;

/* Thread B */
    if (*published == 1) {
        atomic_thread_fence(memory_order_acquire);
        assert(*val == 1); /* may fail */
    }
  1. atomic_thread_fence 只影响原子加载/存储,它对编译器有影响还是只对 cpu 有影响?
  2. 在版本 2 中,要发布的存储是非原子的,它怎么会由于使用 atomic_thread_fence 而导致断言失败,这仅适用于原子加载/存储?
  3. 为什么*val = 1不写成atomic_store_explicit(val, 1, memory_order_relaxed)?

如有需要请帮我修改。谢谢!

c atomic c11 memory-barriers stdatomic
1个回答
1
投票
  1. Fences 确实会影响非原子加载和存储。例如,加载或存储,无论是否是原子的,都不得在获取栅栏之前重新排序。否则围栏将无法建立必要的同步。 “重新排序”包括内存中指令的编译时重新排序,以及运行时乱序执行;栅栏必须抑制它们。

  2. 围栏并不是真的“只用于原子”操作。很简单,假设

    published
    在版本 2 中是非原子的,那么您在
    published
    上有一个数据竞争:您在不同的线程中有两个非原子访问,至少其中一个是写入,并且没有同步到让其中之一发生在另一个之前。所以程序的行为是未定义的。

    围栏在这里不是问题,只是他们没有做任何事情来帮助避免数据竞争。释放/获取栅栏仅在与观察原子存储值的原子加载一起使用时才有效。在其他情况下,它们是无害但也无用的。

  3. 在版本 1 中,

    *val
    可以安全地进行非原子访问。你有一个释放栅栏,后面跟着一个存储(到
    published
    ,值为 1),还有一个负载,如果它观察到存储,后面跟着一个获取栅栏。这正是C17标准中7.17.4p2的设置,所以release fence和acquire fence是同步的(假设真的达到了acquire fence)。因此,您存储
    *val
    发生在您加载
    *val
    之前(如果加载发生的话),所以
    *val
    上没有数据竞争,并且保证加载观察存储值(5.1.2.4第 20 页)。
    published
    上也没有数据竞争,因为它是原子的。

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