为什么这个获取和释放内存栅栏没有给出一致的值?

问题描述 投票:0回答:4

我只是在探索获取和释放内存栅栏的使用,不明白为什么我有时会将值输出为零而不是始终为 2

我多次运行该程序,并假设释放屏障之前的原子存储和获取屏障之后的原子加载将确保值始终同步

#include <iostream>
#include <thread>
#include <atomic>

std::atomic<int>x;



void write()
{


    x.store(2,std::memory_order_relaxed);

    std::atomic_thread_fence(std::memory_order_release);



}

void read()
{

    std::atomic_thread_fence(std::memory_order_acquire);

    // THIS DOES NOT GIVE THE EXPECTED VALUE OF 2 SOMETIMES
    std::cout<<x.load(std::memory_order_relaxed)<<std::endl; 

}

int main()
{

std::thread t1(write);
std::thread t2(read);
t1.join();
t2.join();
return 0;
}

原子变量 x 有时给出 0 值

c++ multithreading c++11 concurrency memory-barriers
4个回答
3
投票

我认为您误解了围栏的用途。栅栏仅在单个执行线程中为编译器和处理器强制执行特定的内存操作顺序。您的获取栅栏不会神奇地使线程等待,直到其他线程执行释放。

一些文献会描述一个线程中的释放操作与另一个线程中的后续获取操作“同步”。关键在于获取操作是后续操作(即获取是在释放“之后”进行的)。如果释放操作是在获取操作之后进行的,则写入和读取操作之间不存在同步关系。

您的代码不能始终如一地返回您所期望的结果的原因是因为线程交错有时会在读取之前排序写入,有时会在写入之前排序读取。

如果您想保证线程

t2
读取线程
2
发布的值
t1
,则必须强制
t2
等待发布发生。教科书的示例几乎总是使用一个保护变量来通知
t2
数据已准备好使用。

我建议您阅读一篇写得非常好的博客文章,关于发布和获取语义以及 Preshing on 编程的 Synchronizes-With Relation


1
投票

看来你滥用了栅栏。您正在尝试将其用作互斥体,对吗?如果您希望代码始终输出 2,那么您只是认为

load
操作永远不会在
save
操作之前执行。但这不是内存栅栏的作用,而是同步原语的作用。

栅栏要棘手得多,它们只是不允许编译器/处理器在一个线程内对某些类型的命令重新排序。最终,两个单独线程的执行顺序是未定义的。


0
投票

原因很简单:你的栅栏什么也做不了,无论如何也不能在这里有任何用处,因为没有任何写入表明栅栏会让获取方(在释放端)可见

简单的答案是读取线程可以先运行,如果运行的话显然不会看到任何写入。

更长的答案是,当你的代码有竞争时,就像任何以不平凡的方式使用互斥体或原子的代码一样,它必须为任何竞争结果做好准备!所以你必须确保不阅读write 写入的值不会破坏您的代码。

额外说明

解释 rel/ack 语义的一种方法是:

  • release 意味着“我已经完成了一些事情”,我将该原子对象设置为某个值以发布该声明;
  • acquire 的意思是“你完成了某件事吗?”,我读取该原子对象以查看它是否包含声明。

因此,在完成任何事情之前释放是没有意义的,并且丢弃包含声明的信息的获取(如

(void)x.load(memory_order_acquire)
中)通常是没有意义的,因为(一般来说)不知道所获取的内容,也就是说不了解所获取的内容就完成了。 (该规则的例外是当线程具有宽松的负载或 RMW 操作时。)


0
投票

0 和 2 都是价值结果。内存排序的目的是确保所有观察者共享共同的事件顺序,而不是顺序始终相同。

© www.soinside.com 2019 - 2024. All rights reserved.