从不冗余。 atomic_thread_fence 实际上比使用 mo_acquire 的负载有更严格的订购要求。它的文档记录很差,但是获取围栏不是单向允许负载的;它保留了栅栏相对两侧的访问之间的读-读和读-写顺序。
另一方面,加载获取只需要在加载和后续加载和存储之间进行排序。只读和读写顺序仅在特定的加载获取之间强制执行。先前的加载/存储(按程序顺序)没有限制。因此负载获取是单向的。
release fence 同样失去了 store 的单向性,保留了 Write-Read 和 Write-Write。请参阅 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);
} }}}
}
编辑:改进了第一个示例,以实际显示跨获取操作的变量。