我试图了解memory_order_relaxed的细节。我指的是这个链接:CPP Reference。
#include <future>
#include <atomic>
std::atomic<int*> ptr {nullptr};
void fun1(){
ptr.store(new int{0}, std::memory_order_relaxed);
}
void fun2(){
while(!ptr.load(std::memory_order_relaxed));
}
int main(){
std::async(std::launch::async, fun1);
std::async(std::launch::async, fun2);
}
问题1:在上面的代码中,fun2在技术上是否可能处于无限循环中,即使设置ptr的线程已经完成运行,它也会将ptr的值视为nullptr?
如果假设,我将上面的代码改为:
#include <future>
#include <atomic>
std::atomic<int> i {0};
std::atomic<int*> ptr {nullptr};
void fun1(){
i.store(1, std::memory_order_relaxed);
i.store(2, std::memory_order_relaxed);
ptr.store(new int{0}, std::memory_order_release);
}
void fun2(){
while(!ptr.load(std::memory_order_acquire));
int x = i.load(std::memory_order_relaxed);
}
int main(){
std::async(std::launch::async, fun1);
std::async(std::launch::async, fun2);
}
相关问题:在上面的代码中,fun2是否有可能将原子i的值看作1,还是确保它会看到值2?
答案 0 :(得分:3)
一个有趣的观察是,使用您的代码,没有实际的并发性;即fun1
和fun2
按顺序运行,原因是在特定条件下(包括使用std::async
启动策略调用std::launch::async
),返回std::future
个对象by std::async
有它的析构函数块,直到启动的函数调用返回。由于忽略了返回对象,因此在语句结束之前调用其析构函数。如果您在main()
中颠倒了两个语句(即在fun2
之前启动fun1
),那么您的程序将被无限循环捕获,因为fun1
永远不会运行。
这种std::future
等待破坏行为有点争议(即使在标准委员会内),因为我认为你并不意味着,我将冒昧地重写{中的2个陈述{1}}(两个例子):
main
这会将实际auto tmp1 = std::async(std::launch::async, fun1);
auto tmp2 = std::async(std::launch::async, fun2);
返回对象销毁推迟到std::future
结束,以便main
和fun1
异步运行。
技术上,fun2是否可以处于无限循环中,即使设置ptr的线程已经完成运行,它也会将ptr的值视为nullptr?
不,fun2
无法做到这一点(在真实平台上,如评论部分所述)。使用非std::atomic
变量,编译器可以(理论上)选择仅将值保留在寄存器中,但是存储std::atomic
并且缓存一致性会将值传播到其他线程。只要您不取消引用指针,就可以使用std::atomic
。
在上面的代码中,fun2是否有可能将原子i的值看作1,还是确保它会看到值2?
保证在变量std::memory_order_relaxed
中看到值2
x
将两个不同的值存储到同一个变量中,但由于存在明确的依赖关系,因此不对这些值进行重新排序。
在fun1
中fun1
ptr.store
std::memory_order_release
阻止i.store(2)
std::memory_order_relaxed
向下移动到其释放障碍之下。在fun2
中ptr.load
std::memory_order_acquire
i.load
阻止std::memory_order_relaxed
x
向上移动到其获取障碍。这可以保证fun2
中的std::memory_order_relaxed
具有值2.
请注意,通过在所有原子上使用x
,可以看到i
的值为0,1或2,具体取决于访问的相对顺序关于ptr.store
和ptr.load
的原子变量Blob
。