了解 memory_order_relaxed

Understanding memory_order_relaxed

我正在尝试了解 memory_order_relaxed 的细节。我指的是 link: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:在上面的代码中,即使设置 ptr 的线程已经完成,fun2 在技术上是否有可能处于无限循环中,它将 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 是否有可能将 atomic i 的值视为 1,或者确定它会看到值 2?

一个有趣的观察是,对于您的代码,没有实际的并发;即 fun1fun2 运行 顺序,原因是在特定条件下(包括使用 std::launch::async 启动策略调用 std::async),std::futurestd::async 编辑的对象 return 具有其析构函数块,直到启动的函数调用 returns。由于您忽略了 return 对象,因此在语句结束之前调用其析构函数。如果您颠倒 main() 中的两个语句(即在 fun1 之前启动 fun2),您的程序将陷入无限循环,因为 fun1 永远不会 运行.

这种 std::future 等待销毁的行为有些争议(即使在标准委员会内部也是如此),因为我认为你不是这个意思,我将冒昧地重写 main 用于(两个示例)到:

auto tmp1 = std::async(std::launch::async, fun1);
auto tmp2 = std::async(std::launch::async, fun2);

这会将实际的 std::future return 对象销毁推迟到 main 结束,以便 fun1fun2 到达 运行 异步。

is it technically possible for fun2 to be in an infinite loop where it sees the value of ptr as nullptr even if the thread that sets ptr has finished running?

不,这在 std::atomic 中是不可能的(在真实平台上,如评论部分所述)。对于非 std::atomic 变量,编译器可以(理论上)选择仅将值保留在寄存器中,但存储 std::atomic 并且缓存一致性会将值传播到其他线程。只要您不取消引用指针,就可以在这里使用 std::memory_order_relaxed

Is it possible in the code above for fun2 to see the value of atomic i as 1 or is it assured that it will see the value 2?

保证在变量 x 中看到值 2。
fun1 将两个不同的值存储到同一个变量,但由于存在明显的依赖关系,因此不会重新排序。

fun1中,ptr.storestd::memory_order_release阻止i.store(2)std::memory_order_relaxed向下移动到其释放屏障以下。在 fun2 中,带有 std::memory_order_acquireptr.load 阻止带有 std::memory_order_relaxedi.load 向上移动越过其获取障碍。这保证 fun2 中的 x 的值为 2。

请注意,通过在 all 原子上使用 std::memory_order_relaxed,可以看到 x 的值为 0、1 或 2,具体取决于相对于 ptr.storeptr.load 访问原子变量 i 的相对顺序。