std :: memory_order和指令顺序,澄清

问题描述 投票:1回答:1

这是对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标志保存中间程序集代码,但它很大,我无法真正确定。

再次澄清,这个问题是关于排序的方式,而不是关于为什么这些机制有用或必要的原因。

c++ c++11 atomic
1个回答
0
投票
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,从而导致无限循环,也可以假定为不循环发生。

© www.soinside.com 2019 - 2024. All rights reserved.