这是对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)))
根据文档,首先关注
...在此存储之后,当前线程中的任何读取或写入操作都无法重新排序...
我看过很少的讲座来了解这个订购问题,我知道为什么现在很重要。我还不太清楚编译器如何翻译订单说明,我认为文档给出的示例也不是特别有用,因为在运行producer
的线程中执行存储操作之后,没有其他指令,因此没有无论如何都会重新排序。但是,我可能还误会了,是否可能表示
std::string* p = new std::string("Hello");
data = 42;
ptr.store(p, std::memory_order_release);
这样翻译的前两行将永远不会在原子存储之后移动?同样,在运行线程的生产器中,是否有可能在原子加载之前没有断言(或等效程序集)被移动?假设我在存储之后有第三条指令,那么这些指令将会发生什么,而原子加载之后已经发生了?
我还尝试编译这样的代码,以使用-S
标志保存中间程序集代码,但它很大,我无法真正确定。
再次澄清,这个问题是关于排序的方式,而不是关于为什么这些机制有用或必要的原因。
std::string* ptr;
int data;
void producer()
{
std::string* p = new std::string("Hello");
data = 42;
ptr = p;
}
void consumer()
{
std::string* p2;
while (!(p2 = ptr))
;
assert(*p2 == "Hello"); // never fires
assert(data == 42); // never fires
}
在producer
中,编译器可以在将赋值分配给ptr之后将其赋值移动到数据中。因为ptr在设置数据之前变为非null,所以可以触发相应的断言。
发布库禁止编译器执行此操作。在
consumer
中,编译器可以将数据的断言自由移动到循环之前。加载获取禁止编译器执行此操作。
与排序无关,但是编译器可以自由地完全省略循环,因为如果在循环开始时ptr为null,则没有任何东西可以有效地使它看起来不为null,从而导致无限循环,也可以假定为不循环发生。