首页 > 解决方案 > C++ memory_order_acquire/release 问题

问题描述

memory_order_acquire最近学习了c++的六种内存命令,对and感到很困惑memory_order_release,下面是来自cpp的一个例子:

#include <thread>
#include <atomic>
#include <cassert>
 
std::atomic<bool> x = {false};
std::atomic<bool> y = {false};
std::atomic<int> z = {0};
 
void write_x() { x.store(true, std::memory_order_seq_cst); }
void write_y() { y.store(true, std::memory_order_seq_cst); }
 
void read_x_then_y() {
     while (!x.load(std::memory_order_seq_cst));

     if (y.load(std::memory_order_seq_cst)) 
         ++z;
}
 
void read_y_then_x() {
     while (!y.load(std::memory_order_seq_cst));

     if (x.load(std::memory_order_seq_cst))
        ++z;
}
 
int main() {
    std::thread a(write_x);
    std::thread b(write_y);
    std::thread c(read_x_then_y);
    std::thread d(read_y_then_x);

    a.join(); b.join(); c.join(); d.join();

    assert(z.load() != 0);  // will never happen
}

在 cpp 参考页面中,它说:

此示例演示了需要顺序排序的情况。

任何其他排序都可能触发断言,因为线程 c 和 d 可能以相反的顺序观察原子 x 和 y 的变化。

所以我的问题是为什么不能在这里使用memory_order_acquirememory_order_release ?memory_order_acquire 和 memory_order_release 提供什么语义?

一些参考资料: https ://en.cppreference.com/w/cpp/atomic/memory_order https://gcc.gnu.org/wiki/Atomic/GCCMM/AtomicSync

标签: c++memory-barriersmemory-modelstdatomic

解决方案


顺序一致性提供了所有顺序一致操作的单一总顺序。因此,如果您在线程 A 中有一个顺序一致的存储,并且在线程 B 中有一个顺序一致的加载,并且存储在加载之前排序(按所述单个总顺序),那么 B 会观察 A 存储的值。所以基本上是顺序一致性保证存储对其他线程“立即可见”。发行商店不提供此保证。

正如 Peter Cordes 正确指出的那样,“立即可见”一词相当不精确。“可见性”源于这样一个事实,即所有 seq-cst 操作都是完全有序的,并且所有线程都遵守该顺序。由于存储和加载是完全排序的,因此在执行后续加载(以单个总顺序)之前,存储的值变得可见。

不同线程中的获取/释放操作之间不存在这样的总顺序,因此不存在可见性保证。只有在获取操作观察到释放操作的值时,操作才会被排序,但不能保证释放操作的值何时对执行获取操作的线程可见。

让我们考虑一下如果我们在这个例子中使用获取/释放会发生什么:

void write_x() { x.store(true, std::memory_order_release); }
void write_y() { y.store(true, std::memory_order_release); }
 
void read_x_then_y() {
     while (!x.load(std::memory_order_acquire));

     if (y.load(std::memory_order_acquire)) 
         ++z;
}
 
void read_y_then_x() {
     while (!y.load(std::memory_order_acquire));

     if (x.load(std::memory_order_acquire))
        ++z;
}
 
int main() {
    std::thread a(write_x);
    std::thread b(write_y);
    std::thread c(read_x_then_y);
    std::thread d(read_y_then_x);

    a.join(); b.join(); c.join(); d.join();

    assert(z.load() != 0);  // can actually happen!!
}

由于我们无法保证可见性,因此线程可能会c观察到x == trueand y == false,而同时线程d可以观察到y == trueand x == false。所以两个线程都不会增加z并且断言会触发。

有关 C++ 内存模型的更多详细信息,我可以推荐我合着的这篇论文:C/C++ 程序员的内存模型


推荐阅读