这是对this one的跟进问题。
我想确切说明指令排序的含义,以及std::memory_order_acquire
,std::memory_order_release
等如何影响指令排序...
在我链接的问题中,已经提供了一些详细信息,但是我觉得所提供的答案不是真的关于订单(这是我想要的更多),而是有点动机,为什么要这样做等等。
我将引用相同的示例作为引用
#include <thread>
#include <atomic>
#include <cassert>
#include <string>
std::atomic<std::string*> ptr;
int data;
void producer()
{
std::string* p = new std::string("Hello");
data = 42;
ptr.store(p, std::memory_order_release);
}
void consumer()
{
std::string* p2;
while (!(p2 = ptr.load(std::memory_order_acquire)))
;
assert(*p2 == "Hello"); // never fires
assert(data == 42); // never fires
}
int main()
{
std::thread t1(producer);
std::thread t2(consumer);
t1.join(); t2.join();
}
简而言之,我想弄清楚两行指令顺序到底发生了什么
ptr.store(p, std::memory_order_release);
和
while (!(p2 = ptr.load(std::memory_order_acquire)))
根据文档重点关注第一个
... no reads or writes in the current thread can be reordered after this store ...
我看过很少的讲座来了解这个订购问题,我知道为什么现在很重要。我还不太清楚编译器如何翻译订单说明,我认为文档给出的示例也不是特别有用,因为在运行
producer
的线程中执行存储操作之后,没有其他指令,因此不会有任何其他指令仍然重新订购。但是也有可能我误会了,是否有可能意味着std::string* p = new std::string("Hello");
data = 42;
ptr.store(p, std::memory_order_release);
这样翻译的前两行将永远不会在原子存储之后移动吗?
同样,在运行线程的生产器中,是否有可能在原子加载之前没有断言(或等效程序集)被移动?假设我在存储之后有第三条指令,那么这些指令将会发生什么,而原子加载之后已经发生了什么呢?
我也尝试过编译这样的代码,以使用
-S
标志保存中间程序集代码,但是它相当大,我无法确定。再次澄清一下,这个问题是关于排序的方式,而不是关于这些机制为何有用或必要的原因。
最佳答案
我知道,当涉及到内存排序时,人们通常会争辩是否可以以及如何对操作进行重新排序,但是我认为这是错误的方法! C++标准没有说明如何对指令进行重新排序,而是定义了事前发生关系,该关系本身是基于事前进行顺序,与线程之间发生同步以及在事前发生线程之间的关系。
从存储释放读取值的获取负载与该获取负载同步,因此建立了事前发生关系。由于事前发生关系的可传递性,因此在“存储释放之前”进行“按先后顺序”的操作,也在“获取负载之前”进行“在发生之前”的操作。关于使用原子实现的正确性的任何争论都应始终依赖于事前发生关系。 是否以及如何对指令重新排序仅是对事前发生关系应用规则的结果。
有关C++内存模型的详细说明,请查看Memory Models for C/C++ Programmers。
关于c++ - std::memory_order和指令顺序,澄清,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59651328/