在具有相同顺序的原子加载/存储之前使用 std::atomic_thread_fence 总是多余的吗?

Jos*_*vin 5 c++ multithreading atomic lockless instruction-reordering

鉴于:

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...
}
Run Code Online (Sandbox Code Playgroud)

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

Hum*_*ago 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);
Run Code Online (Sandbox Code Playgroud)

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

以下是使用 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);
  } }}}
}
Run Code Online (Sandbox Code Playgroud)

如果我们用 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);
  } }}}
}
Run Code Online (Sandbox Code Playgroud)

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