C++ 围栏在c++;

C++ 围栏在c++;,c++,multithreading,atomic,memory-fences,C++,Multithreading,Atomic,Memory Fences,我一直在努力理解fences实际上是如何强制代码同步的 例如,假设我有这个代码 bool x = false; std::atomic<bool> y; std::atomic<int> z; void write_x_then_y() { x = true; std::atomic_thread_fence(std::memory_order_release); y.store(true, std::memory_order_relaxed);

我一直在努力理解fences实际上是如何强制代码同步的

例如,假设我有这个代码

bool x = false;
std::atomic<bool> y;
std::atomic<int> z;
void write_x_then_y()
{
    x = true;
    std::atomic_thread_fence(std::memory_order_release);
    y.store(true, std::memory_order_relaxed);
}
void read_y_then_x()
{
    while (!y.load(std::memory_order_relaxed));
    std::atomic_thread_fence(std::memory_order_acquire);
    if (x)
        ++z;
}
int main()
{
    x = false;
    y = false;
    z = 0;
    std::thread a(write_x_then_y);
    std::thread b(read_y_then_x);
    a.join();
    b.join();
    assert(z.load() != 0);
}
boolx=false;
std::原子y;
std::原子z;
无效写入
{
x=真;
std::原子线程围栏(std::内存命令释放);
y、 存储(正确,标准::内存\u顺序\u松弛);
}
无效读_y_然后_x()
{
而(!y.load(std::memory_order_released));
std::原子线程围栏(std::内存顺序获取);
if(x)
++z;
}
int main()
{
x=假;
y=假;
z=0;
std::线程a(写入x然后y);
std::线程b(读_y_然后_x);
a、 join();
b、 join();
断言(z.load()!=0);
}
因为发布围栏后面是一个原子存储操作,而获取围栏前面是一个原子加载,所以所有内容都按照预期进行同步,断言不会触发

但如果y不是这样的原子变量

bool x;
bool y;
std::atomic<int> z;
void write_x_then_y()
{
    x = true;
    std::atomic_thread_fence(std::memory_order_release);
    y = true;
}
void read_y_then_x()
{
    while (!y);
    std::atomic_thread_fence(std::memory_order_acquire);
    if (x)
        ++z;
}
boolx;
布尔伊;
std::原子z;
无效写入
{
x=真;
std::原子线程围栏(std::内存命令释放);
y=真;
}
无效读_y_然后_x()
{
而(!y);
std::原子线程围栏(std::内存顺序获取);
if(x)
++z;
}
然后,我听说可能会有一场数据竞赛。但为什么呢? 为什么发布fences之后必须有一个原子存储,而获取fences之前必须有一个原子加载,这样代码才能正确同步


如果有人能提供一个执行场景,其中数据竞争导致断言触发,我也将不胜感激。对于您的第二个代码段,没有真正的数据竞争是一个问题。这个片段就可以了。。。如果编译器将从编写的机器代码中生成机器代码

但是编译器可以自由生成任何机器代码,这与单线程程序的原始机器代码相同

例如,编译器可以注意到,
y
变量在(!y)循环时不会在
中更改,因此它可以加载此变量一次以注册,并在下一次迭代中仅使用该寄存器。因此,如果最初
y=false
,您将得到一个无限循环

另一个可能的优化是删除
while(!y)
循环,因为它不包含对易失性或原子变量的访问,也不使用同步操作。(C++标准规定,任何正确的程序都应该最终执行上面指定的操作之一,因此编译器在优化程序时可以依赖这一事实)

等等

更一般地,C++标准指定对任何非原子变量的并发访问导致未定义的行为,这就像“保证被清除”。这就是为什么应该使用原子

y
变量的原因


另一方面,变量
x
不需要是原子的,因为由于内存限制,对它的访问不是并发的。

停止。看这个视频。再看一遍,然后接受指定内存顺序是一种危险的时间浪费:根据内存模型,无序、并发地访问非原子变量与此无关。围栏提供了正确代码的同步功能,但它们不会阻止UB。老实说,这个问题的最佳答案可能是,“因为这是规则所说的,系统可以自由破坏不遵守规则的代码”。非常好的视频@RichardHodges。它帮助解释了很多我不知道的acquire发布语义。我通常不会指定内存顺序(我只是使用默认的顺序一致性模型:)但我只是好奇,为什么上面的代码需要这样奇怪的实现才能避免数据竞争。@user3769877我真的认为,对于任何准备编写多线程代码的人来说,演示应该是强制性的。