atomic_thread_fence(memory_order_release) 与使用 memory_order_acq_rel 有区别吗?

Is atomic_thread_fence(memory_order_release) different from using memory_order_acq_rel?

cppreference.com 提供 this note about std::atomic_thread_fence(强调我的):

atomic_thread_fence imposes stronger synchronization constraints than an atomic store operation with the same std::memory_order.

While an atomic store-release operation prevents all preceding writes from moving past the store-release, an atomic_thread_fence with memory_order_release ordering prevents all preceding writes from moving past all subsequent stores.

我理解这条注释的意思是 std::atomic_thread_fence(std::memory_order_release) 不是单向的,就像商店发布一样。这是一个双向栅栏,可防止栅栏任一侧 的商店重新排序,越过栅栏另一侧的商店。

如果我理解正确的话,这个围栏似乎提供了与 atomic_thread_fence(memory_order_acq_rel) 相同的保证。它是一个 "upward" 栅栏,一个 "downward" 栅栏。

std::atomic_thread_fence(std::memory_order_release)std::atomic_thread_fence(std::memory_order_acq_rel) 在功能上有区别吗?还是仅仅是美学上的差异,以记录代码的目的?

独立栅栏比具有相同排序约束的原子操作强加了排序,但这不会改变强制执行排序的方向。

Bot 一个原子释放操作和一个独立的释放栅栏是单向的, 但是原子操作对自身进行排序,而原子栅栏则对其他商店进行排序。

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

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

// memory operations A

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

// store B

这保证了 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

这保证了 A 中的任何内存操作都不能(明显地)用 any 在释放栅栏之后排序的存储重新排序。 在这里,B 的存储不能再用 A 中的任何内存操作重新排序,因此,释放栅栏比原子释放操作更强大。 但它也是单向的,因为来自 X 的负载仍然可以通过 A 中的任何内存操作重新排序。

区别很细微,通常原子释放操作优于独立释放栅栏。

独立获取栅栏的规则类似,只是它强制执行相反方向的排序并在负载上运行:

// loads B

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

// memory operations A

在独立获取栅栏之前排序的 任何 加载都不能对 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

但是,一旦您意识到 A 中的商店仍然可以通过 B 中的负载重新订购,这就会变得非常棘手。 Acq/rel 应该避免栅栏以支持常规的原子操作,或者更好的互斥锁。

cppreference.com 在您引用的段落中有一些错误。我在下面突出显示了它们:

atomic_thread_fence imposes stronger synchronization constraints than an atomic store operation with the same std::memory_order. While an atomic store-release operation prevents all preceding writes(should be memory operations, i.e. including reads and writes) from moving past the store-release (the complete sentence should be: the store-release operation itself), an atomic_thread_fence with memory_order_release ordering prevents all preceding writes(should be memory operations, i.e. including reads and writes) from moving past all subsequent stores.

换句话说:

The release operation actually places fewer memory ordering constraints on neighboring operations than the release fence. A release operation only needs to prevent preceding memory operations from being reordered past itself, but a release fence must prevent preceding memory operations from being reordered past all subsequent writes. Because of this difference, a release operation can never take the place of a release fence.

本文引用自here

这是我对以下文字的意图的解释,我认为这是有意的。此外,该解释在内存模型方面是正确的,但仍然很糟糕 ,因为它是一个不完整的解释

While an atomic store-release operation prevents all preceding writes from moving past the store-release, an atomic_thread_fence with memory_order_release ordering prevents all preceding writes from moving past all subsequent stores.

"store" 与 "writes" 的使用是有意的:

  • "store",在这里,意味着存储在 std::atomic<> 对象上(不仅仅是对 std::atomic<>::store 的调用,还有等同于 .store(value) 或 RMW 的赋值原子操作);
  • "write",在这里,意味着任何内存写入,无论是正常的(非原子的)还是原子的。

It's a bidirectional fence, preventing stores on either side of the fence from reordering past a store on the other side of the fence.

不,您错过了一个本质区别,因为它只是隐含的;表达不清楚,太微妙 - 不适合作为教学文本!

它说释放栅栏 不是 对称的:以前的内存副作用,称为 "writes",受以下 atomic store 操作。

即使有这样的澄清,它仍然不完整,所以这是一个错误的解释:它强烈建议存在释放栅栏只是为了确保写入(和仅写入)完成 .事实并非如此。

释放操作就是我所说的:"I'm done there"信号。它表示所有先前的内存操作已完成、完成、可见。重要的是要理解,不仅修改(可以通过查看内存状态检测)是有序的,内存中的所有内容都需要

许多关于线程原语的文章都存在这种缺陷。