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

鉴于:

std::atomic<uint64_t>B
void f()
{
标准::原子线程围栏(标准::内存顺序::内存顺序获取);
uint64\u t a=b.load(标准::内存顺序::内存顺序获取);
//代码使用。。。
}

删除对std::atomic_thread_fence的调用是否有任何效果?如果有,有没有一个简洁的例子?请记住,其他函数可能存储/加载到b并调用f

从不冗余atomic_thread_fence实际上比使用mo_acquire的负载具有更严格的订购要求。它的记录很差,但获取围栏不允许单向装载;它保留了围栏两侧访问之间的读写顺序

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

释放围栏同样会丢失存储的单向许可,从而保留写-读和写-写。见杰夫·普雷辛的文章https://preshing.com/20130922/acquire-and-release-fences/.

顺便说一句,看起来你的篱笆放错一边了。见普雷辛的另一篇文章https://preshing.com/20131125/acquire-and-release-fences-dont-work-the-way-youd-expect/. 使用acquire加载时,加载发生在acquire之前,因此使用fences时,它将如下所示:

uint64\u t a=b.load(标准:内存顺序:内存顺序);
标准::原子线程围栏(标准::内存顺序::内存顺序获取);

请记住,该版本并不保证可见性。该版本所做的一切就是保证对不同变量的写入在其他线程中可见的顺序。(没有这一点,其他线程可以观察到似乎违反因果关系的顺序。)

下面是一个使用CppMem工具的示例(http://svr-pes20-cppmem.cl.cam.ac.uk/cppmem/). 第一个线程是SC,所以我们知道写操作是按这个顺序发生的。所以如果c==1,那么a和b也应该是1。CppMem给出了;48次处决;1“一致、无种族歧视”;,指示第二个线程可能看到c==1&amp&b==0&amp&a==0。这是因为c.load允许在a.load之后重新排序,渗透到b.load

int main(){
原子_int a=0;
原子_int b=0;
原子_int c=0;
{{{ {
a、 仓库(1个,生产顺序cst);
b、 仓库(1个,生产顺序cst);
c、 仓库(1个,生产顺序cst);
} ||| {
c、 加载(mo_松弛)。读取值(1);
b、 加载(mo_获取)。读取值(0);
a、 加载(mo_松弛)。读取值(0);
} }}}
}

如果我们用隔水栅栏替换采集负载,c.load不允许在a.load之后重新排序。CppMem给出了;8次处决;“不一致”;确认这是不可能的

int main(){
原子_int a=0;
原子_int c=0;
{{{ {
a、 仓库(1个,生产顺序cst);
c、 仓库(1个,生产顺序cst);
} ||| {
c、 加载(mo_松弛)。读取值(1);
原子线围栏(mo);
a、 加载(mo_松弛)。读取值(0);
} }}}
}

编辑:改进了第一个示例,以实际显示与获取操作交叉的变量

发表评论