具有多个存储的内存顺序

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

考虑下面的例子。假设

barrier
初始化为 0。

有一个生产者线程和两个消费者线程不断检查

barrier
。如果设置了障碍,它们就会减少
runcnt
。生产者线程等待
runcnt
达到 0。我对生产者内部多个存储操作的顺序感到困惑。

如果顺序像写的那样,我认为代码会按预期运行。但是,如果

barrier
存储在
runcnt
存储之前重新排序,则断言检查似乎会失败。 我错过了什么吗?有办法解决这个问题吗?


extern atomic<int> barrier[2];
atomic_int runcnt{0};

void producer() {
    runcnt.store(2, memory_order_relaxed);
    barrier[0].store(1, memory_order_relaxed);
    barrier[1].store(1, memory_order_relaxed);
    
    while (runcnt.load(memory_order_relaxed)) {
        cpu_pause();
    }
}

void consumer(unsigned index) {
   while (true) {
    if (barrier[index].exchange(false, memory_order_relaxed)) {
      int prev = runcnt.fetch_sub(1, memory_order_relaxed);
      assert(prev > 0);
    }
   }
}

更新

正如 @peter-cordes 指出的,与单向的常规发布存储不同,

std::atomic_thread_fence(release)
充当存储之间的双向屏障(负载仍然可以重新排序)。

因此,这是固定代码:

void producer() {
    runcnt.store(2, memory_order_relaxed);

    std::atomic_thread_fence(release);  // <- Prevents reordering of barrier stores before runcnt.store. 

    barrier[0].store(1, memory_order_relaxed);
    barrier[1].store(1, memory_order_relaxed);
    
    while (runcnt.load(memory_order_relaxed)) {
        cpu_pause();
    }
}
c++ atomic memory-barriers lock-free
1个回答
3
投票

如果顺序与所写的一样 - 您的意思是如果操作碰巧以

seq_cst
也允许的顺序变得可见?当然,您可以在所有操作中使用
seq_cst
来要求这一点。

我认为读者方面的最小值是

barrier[i].exchange
acquire

在编写器方面,两个

barrier[i]
存储都需要是
release
,或者在
std::atomic_thread_fence(release)
之后放置一个
runcnt.store
,所以它位于它和任一障碍存储之间。

这使得

exchange
与它加载的任何
barrier
存储同步,假设它加载了
1
,因此
if
主体完全运行。

runcnt.store(relaxed)
barrier[0].store(release)
;在 C++ 内存模型中,甚至在针对某些 ISA 进行编译时,
barrier[1].store(relaxed)
不够:最终的宽松存储可以通过发布存储,因为它只是单向屏障。这是栅栏和操作之间的关键区别:https://preshing.com/20131125/acquire-and-release-fences-dont-work-the-way-youd-expect/。即使做中间存储
seq_cst
也是不够的,它仍然只是一个操作,而不是2向围栏。

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