atomic_thread_fence(memory_order_release)与使用memory_order_acq_rel是否有所不同?

Dre*_*ann 3 c++ atomic memory-model memory-barriers language-lawyer

cppreference.com提供以下有关std::atomic_thread_fence(强调我的)注释

atomic_thread_fence比具有相同std :: memory_order的原子存储操作施加更强的同步约束。

虽然原子存储释放操作可以防止所有先前的写操作移出存储释放,但是具有memory_order_release排序的atomic_thread_fence可以防止所有先前的写操作移过所有后续存储

我了解此注释的意思 std::atomic_thread_fence(std::memory_order_release)不是像存储版本那样单向的。这是双向栅栏,可防止栅栏两侧的商店重新排序,使其越过栅栏另一侧的商店。

如果我正确理解这一点,那么这道篱笆似乎可以提供同样的保证atomic_thread_fence(memory_order_acq_rel)。它是“向上”的栅栏,是“向下”的栅栏。

有没有之间的功能差异std::atomic_thread_fence(std::memory_order_release)std::atomic_thread_fence(std::memory_order_acq_rel)?还是为了记录代码的目的,差异仅仅是美观?

LWi*_*sey 5

独立的篱笆比具有相同排序约束的原子操作要强得多的排序,但这不会改变强制执行的方向。

Bot原子释放操作和独立释放隔离栅是单向的,但是原子操作相对于其自身是有序的,而原子隔离栅是相对于其他商店强加的。

例如,具有释放语义的原子操作:

std::atomic<int> sync{0};

// memory operations A

sync.store(1, std::memory_order_release);

// store B
Run Code Online (Sandbox Code Playgroud)

这保证了A(加载和存储)的任何内存操作部分都不能(与原子存储本身一样)重新排序。但是它是单向的,没有排序规则适用于在原子操作之后排序的内存操作。因此,仍然可以使用A中的任何存储操作对存储B进行重新排序。

独立的发布防护会更改此行为:

// memory operations A

std::atomic_thread_fence(std::memory_order_release);

// load X

sync.store(1, std::memory_order_relaxed);

// stores B
Run Code Online (Sandbox Code Playgroud)

这保证了(发布)在发布围栏之后排序的任何存储都无法(可视地)对A中的任何存储操作进行重新排序。在这里,无法再使用A中的任何存储操作对存储到B的对象进行重新排序,因此,释放范围比原子释放操作更强大。但是它也是单向的,因为来自X的负载仍然可以通过A中的任何内存操作进行重新排序。

两者之间的差别很小,通常原子释放操作优于独立的释放栅栏。

独立获取栅栏的规则相似,不同之处在于它强制执行相反方向的排序并在负载下运行:

// loads B

sync.load(std::memory_order_relaxed);
std::atomic_thread_fence(std::memory_order_acquire);

// memory operations A
Run Code Online (Sandbox Code Playgroud)

在独立获取围墙之前,任何已排序的负载都无法对A中的内存操作进行重新排序。

具有std::memory_order_acq_rel订购功能的独立围栏结合了获取和发布围栏的逻辑。

// memory operations A
// load A

std::atomic_thread_fence(std::memory_order_acq_rel);

// store B
//memory operations B
Run Code Online (Sandbox Code Playgroud)

但是,一旦您意识到仍然可以用B中的负载对A中的商店进行重新排序,这将变得异常棘手。应该避免使用Acq / rel栅栏,而推荐使用常规原子操作,甚至更好的互斥体。