_Compiler_barrier() 在 32 位读取上的用途

Purpose of _Compiler_barrier() on 32bit read

我一直在逐步完成在 VS2017 上使用 64 位项目分配给 atomic_long 类型时涉及的函数调用。我特别想看看当我将 atomic_long 复制到 none 原子变量时会发生什么,以及它周围是否有任何锁定。

atomic_long ll = 10;
long t2 = ll;

最终它以这个调用结束(我删除了一些 ifdefed 出来的代码)

inline _Uint4_t _Load_seq_cst_4(volatile _Uint4_t *_Tgt)
    {   /* load from *_Tgt atomically with
            sequentially consistent memory order */
    _Uint4_t _Value;

    _Value = *_Tgt;
    _Compiler_barrier();

    return (_Value);
    }

现在,我读到 from MSDN 32 位值的普通读取将是原子的:

Simple reads and writes to properly-aligned 32-bit variables are atomic operations.

...这解释了为什么没有 Interlocked 功能只用于阅读;只有 changing/comparing 的那些。我想知道 _Compiler_barrier() 位在做什么。这是 #defined as

__MACHINE(void _ReadWriteBarrier(void))

...我在 MSDN 上再次发现这个

Limits the compiler optimizations that can reorder memory accesses across the point of the call.

但我不明白,因为除了 return 调用之外没有其他内存访问;编译器肯定不会将赋值移动到下面,对吗?

有人可以澄清这个障碍的目的吗?

_Load_seq_cst_4 是一个 inline 函数 。编译器屏障用于阻止内联到的调用函数中的后续代码重新排序。

例如,考虑阅读 SeqLock. (Over-simplified from this actual implementation).

#include <atomic>
atomic<unsigned> sequence;
atomic_long  value;

long seqlock_try_read() {
    // this would normally be the body of a retry-loop;
    unsigned seq1 = sequence;
    long tmpval = value;
    unsigned seq2 = sequence;

    if (seq1 == seq2 && (seq1 & 1 == 0)
        return tmpval;
    else
        // writer was modifying it, we should retry the loop
}

如果我们不阻止编译时重新排序,编译器可以将 sequence 的两次读取合并为一次访问,可能像这样

    long tmpval = value;
    unsigned seq1 = sequence;
    unsigned seq2 = sequence;

这会破坏锁定机制(编写器在修改数据之前递增 sequence 一次,然后在完成后再次递增)。读者是完全无锁的,但这不是 "lock-free" 算法,因为如果作者在更新过程中卡住,读者将无法阅读任何内容。

障碍 within 每个 load 函数阻止内联后与其他事物重新排序。

(C++11 内存模型很弱,但 x86 内存模型很强,只允许 StoreLoad 重新排序。用后面的 loads/stores 阻止编译时重新排序足以给你一个 acquire /运行时顺序一致性加载。)


顺便说一句,一个更好的例子可能是某些非 atomic 变量在看到 atomic 标志中的特定值后 read/written。 MSVC 可能已经避免了原子访问的重新排序或合并,并且在 seqlock 中,受保护的数据也必须是 atomic.