C++的happens-before关系和memory_order_consume

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

考虑以下使用

std::memory_order_consume
同步数据的示例:

struct X
{
    int i;
    std::string s;
};

std::atomic<X*> p;
std::atomic<int> a;

void create_x()
{
    X* x=new X;
    x->i=42;
    x->s="hello";
    a.store(99,std::memory_order_relaxed);                     //1
    p.store(x,std::memory_order_release);                      //2
}

void use_x()
{
    X* x;
    while(!(x=p.load(std::memory_order_consume)))              //3
        std::this_thread::sleep(std::chrono::microseconds(1));

    assert(x->i==42);                                          //4
    assert(x->s=="hello");                                    // 5

    assert(a.load(std::memory_order_relaxed)==99);            // 6
}

int main()
{
    std::thread t1(create_x);
    std::thread t2(use_x);
    t1.join();
    t2.join();
}

Anthony Williams 在他的《Concurrency in Action》一书中给出了以下解释:

即使对 a 1 的存储在对 p 2 的存储之前排序,并且 p 的存储标记为 memory_order_release,p 3 的负载为 标记为内存_顺序_消耗。这意味着商店只能 发生在依赖于值的表达式之前 从p加载。这意味着对数据成员的断言 X结构(4和5)保证不会着火,因为 p 通过变量 x 承载对这些表达式的依赖。在 另一方面,对 6 值的断言可能会触发,也可能不会触发; 此操作不依赖于从 p 加载的值,因此 无法保证所读取的值。这一点特别 很明显,因为它被标记为 memory_order_relaxed。

这个解释是有道理的,但是当我尝试根据cppreference定义的发生之前关系来思考它时,我无法得出相同的结论:

  • 商店 1 和 2 之间存在“sequence-before”和“happens-before”关系
  • 2和3之间存在依赖排序前和线程间发生前关系
  • 由于线程间发生之前关系与序列之前关系相结合,因此存储 1 线程间发生在加载 3 之前,因此 1 发生在 3 之前
  • 按照程序顺序,加载 3 排在加载 6 之前,因此 3 发生在 6 之前
  • 正如我们得出的结论,1 发生在 3 之前,3 发生在 6 之前,那么根据传递性,1 发生在 6 之前

最后的说法似乎与书中所写的相矛盾。我的逻辑链是否有错误,或者存储 1 发生在加载 6 之前并不一定意味着 1 的影响对 6 可见?

c++ concurrency atomic memory-barriers
1个回答
0
投票

你的逻辑错误在最后一步。 C++23(N4950 最终草案)中发生之前的定义是:

评估 A 发生在评估 B 之前(或者等效地,B 发生在 A) 之后,如果:

— A 排在 B 之前,或者

— 线程间 发生在 B 之前。

注意,有not第三个子句说“如果A发生在X之前并且X发生在B之前”。 发生之前关系not定义为传递性。

所以确实 1 发生在 3 之前并且 3 发生在 6 之前,但并不意味着 1 发生在 6 之前。

还要注意,您无法显示 1 个线程间发生在 6 个线程之前。

(当没有消耗操作时,因此没有操作在任何其他操作之前都是依赖排序的,那么您可以证明发生之前是传递性的,并且是sequenced-before和synchronized-with并集的传递闭包。这匹配 simple-happens-before 的定义方式,并且有一个注释指出了这一点。)

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