4

鉴于:

std::atomic<uint64_t> b;

void f()
{
    std::atomic_thread_fence(std::memory_order::memory_order_acquire);

    uint64_t a = b.load(std::memory_order::memory_order_acquire);

    // code using a...
}

可以去掉调用std::atomic_thread_fence有什么影响吗?如果是这样,有一个简洁的例子吗?请记住,其他功能可能会存储/加载到b和调用f.

4

1 回答 1

2

从不冗余。atomic_thread_fence实际上比负载更严格的订购要求mo_acquire。它的文档记录很差,但是获取围栏不是单向允许负载的;它保留了栅栏相对两侧的访问之间的读-读和读-写顺序。

另一方面,加载获取只需要在该加载与后续加载和存储之间进行排序。只读和读写顺序仅在特定的加载获取之间强制执行。先前的加载/存储(按程序顺序)没有限制。因此,负载获取是单向的。

发布栅栏同样失去了对存储的单向允许性,保留了写-读和写-写。请参阅 Jeff Preshing 的文章https://preshing.com/20130922/acquire-and-release-fences/

顺便说一句,看起来你的栅栏在错误的一侧。请参阅 Preshing 的另一篇文章https://preshing.com/20131125/acquire-and-release-fences-dont-work-the-way-youd-expect/。使用获取加载,加载发生在获取之前,因此使用栅栏它看起来像这样:

uint64_t a = b.load(std::memory_order::memory_order_relaxed);
std::atomic_thread_fence(std::memory_order::memory_order_acquire);

请记住,发布并不能保证可见性。所有发布所做的都是保证写入不同变量的顺序在其他线程中变得可见。(没有这个,其他线程可以观察到似乎违反因果关系的顺序。)

这是使用 CppMem 工具 ( http://svr-pes20-cppmem.cl.cam.ac.uk/cppmem/ )的示例。第一个线程是 SC,所以我们知道写入是按这个顺序发生的。因此,如果 c==1,那么 a 和 b 也应该为 1。CppMem 给出“48 次执行;1 次一致,无竞争”,表明第二个线程有可能看到 c==1 && b==0 && a==0。这是因为c.load允许重新排序之后a.load,渗透过去b.load

int main() {
  atomic_int a = 0;
  atomic_int b = 0;
  atomic_int c = 0;

  {{{ {
    a.store(1, mo_seq_cst);
    b.store(1, mo_seq_cst);
    c.store(1, mo_seq_cst);
  } ||| {
    c.load(mo_relaxed).readsvalue(1);
    b.load(mo_acquire).readsvalue(0);
    a.load(mo_relaxed).readsvalue(0);
  } }}}
}

如果我们用 aquire-fence 替换 acquire-load,c.load则不允许在 之后重新排序a.load。CppMem 给出“8 次执行;不一致”,确认这是不可能的。

int main() {
  atomic_int a = 0;
  atomic_int c = 0;

  {{{ {
    a.store(1, mo_seq_cst);
    c.store(1, mo_seq_cst);
  } ||| {
    c.load(mo_relaxed).readsvalue(1);
    atomic_thread_fence(mo_acquire);
    a.load(mo_relaxed).readsvalue(0);
  } }}}
}

编辑:改进了第一个示例以实际显示跨获取操作的变量。

于 2021-11-01T10:19:17.617 回答