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

时间:2018-07-12 22:12:38

标签: c++ language-lawyer atomic

cppreference.com提供了this note about 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)之间是否存在功能差异?还是为了记录代码的目的,差异仅仅是美观?

3 个答案:

答案 0 :(得分:4)

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

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

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

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中的任何存储操作都不能(按明显顺序)与发布围栏之后排序的存储的任何一起重新排序。 在这里,无法再使用A中的任何存储操作对存储到B的对象进行重新排序,因此,释放范围比原子释放操作更强大。 但是它也是单向的,因为来自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

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

答案 1 :(得分:0)

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

  与具有相同std :: memory_order的原子存储操作相比,

atomic_thread_fence施加了更强的同步约束。虽然原子存储释放操作会阻止所有先前的写入(应为内存操作,即包括读写)超过存储释放(完整的句子应为:存储释放操作本身),具有memory_order_release排序的atomic_thread_fence可以防止所有之前的写入(应该是内存操作,即包括读写操作) )从所有后续商店中移出。

释义:

  

发布围栏相比,发布操作实际上对相邻操作施加的内存排序约束更少。释放操作仅需要防止对先前的内存操作进行重新排序,而释放围栏必须防止对先前的所有内存操作进行重新排序。由于存在这种差异,所以释放操作永远无法代替释放围栏。

这引自:https://preshing.com/20131125/acquire-and-release-fences-dont-work-the-way-youd-expect/

答案 2 :(得分:0)

这是我对以下文本的意图的解释,我认为这是意图。同样,这种解释在内存模型方面是正确的,但由于它是不完整的解释,因此仍然很糟糕。

  

尽管原子存储释放操作阻止了之前的所有写操作   越过了商店发行版,atomic_thread_fence   memory_order_release排序可防止之前的所有写操作   越过所有后续商店。

故意使用“存储”与“写入”:

  • “存储”在这里是指在std::atomic<>对象上的存储(不仅仅是对std::atomic<>::store的调用,还相当于.store(value)或RMW原子操作的赋值);
  • “写入”在这里是指任何内存写入,无论是普通的(非原子的)还是原子的。
  

这是双向围栏,可防止在商店的两侧存储   通过重新排列栅栏另一边的商店来重新排列栅栏。

不,您错过了本质上的区别,因为它只是隐含的。表达方式不清楚,太微妙-不利于教学文字!

它说释放栅栏不是 对称:以前的内存副作用(称为“写入”)受以下 atomic store 操作的约束。

即使做出了这样的澄清,它仍然是不完整的,因此是一个错误的解释:它强烈建议存在发布限制,只是为了确保写入(和仅写入)已完成。事实并非如此。

释放操作就是我所说的:“我在那儿完成了”信号。它表示所有先前的内存操作已完成,已完成,可见。重要的是要理解,不仅修改(通过查看内存状态可以检测到)是有序的,内存上的所有内容都必须

许多关于线程原语的文章都是有缺陷的。