我只是探索使用获取和释放内存围栏的方法,不理解为什么我有时有时将输出值设为零而不是始终将值设为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
答案 0 :(得分:2)
我认为您误解了围栏的目的。栅栏仅在单个执行线程中为编译器和处理器强制执行某种内存操作顺序。您的获取栅栏不会神奇地使线程等待,直到另一个线程执行释放。
一些文献将描述一个线程中的释放操作与另一个线程中的后续获取操作“同步”。这样做的关键是,获取动作是后续动作(即,在发布之后“订购”获取指令)。如果在获取操作之后命令释放操作,则在写操作和读操作之间没有同步关系。
您的代码不能始终如一地返回期望的原因是因为线程交错有时会在读取之前对写入进行排序,有时会在写入之前进行读取。
如果要确保线程t2
读取线程2
发布的值t1
,则必须强制t2
等待发布即将发生。教科书示例几乎总是使用保护变量来通知t2
数据已准备就绪。
我建议您在Preshing on Programming的The Synchronizes-With Relation上阅读一篇写得很好的有关发布和获取语义以及同步关系的博客文章。
答案 1 :(得分:1)
看起来您滥用栅栏。您正在尝试将其用作互斥体,对吗?如果希望代码始终输出2,则只需认为load
操作永远不会在save
操作之前执行。但这不是内存屏障的作用,而是同步原语的作用。
栅栏要棘手得多,它们只是不允许编译器/处理器在一个线程内对某些类型的命令进行重新排序。最终,两个独立线程的执行顺序是不确定的。
答案 2 :(得分:0)
原因很简单:您的篱笆完全什么也做不了,在这里也不能有任何用处,因为没有任何记载说篱笆会使收货方(在发行方)可见。< / p>
简单的答案是,读取线程可以首先运行,并且显然不会看到任何写入。
更长的答案是当您的代码出现竞争时,因为任何以非平凡的方式使用互斥或原子的代码,必须为任何竞争结果做好准备!< / strong>因此,您必须确保不读取写操作所写的值不会破坏您的代码。
附加说明
一种解释rel / ack语义的方法是:
因此,在您完成任何事情之前放行是没有意义的,并且丢弃(void)x.load(memory_order_acquire)
中的包含索偿要求的信息通常是没有意义的,因为(通常)不知道所获取的内容是什么。说说完成了什么。 (该规则的例外是当线程进行宽松的负载或RMW操作时。)