以RMW为首的释放序列是否建立同步关系?

问题描述 投票:0回答:1
#include <iostream>
#include <atomic>
#include <thread>
#include <cassert>
int main(){
    std::atomic<int> v{0};
    int x = 0;
    std::thread t1([&](){
        x = 1;
        v.exchange(1,std::memory_order::release); // #1
    });
    std::thread t2([&](){
        int expected = 1;
        while(!v.compare_exchange_strong(expected,3,std::memory_order::relaxed)){}  // #2
        v.fetch_sub(1,std::memory_order::relaxed); // #3
    });
    std::thread t3([&](){
        int expected = 2;
        while(!v.compare_exchange_strong(expected,10,std::memory_order::relaxed)){} // #4
        assert(x ==1);
    });
    t1.join();
    t2.join();
    t3.join();
}

[介绍.races] p5 说:

以原子对象 M 上的释放操作 A 为首的释放序列是 M 的修改顺序中副作用的最大连续子序列,其中第一个操作是 A,后续的每个操作都是原子读-修改-写操作。

在本例中,

v
的修改顺序是
{0, 1, 3, 2,10}
,因为
#4
读取了
#3
写入的值,并且
#3
可以考虑在以
#1
为首的释放顺序中,因此,断言永远不会失败,这是正确的理解吗?

c++ language-lawyer atomic
1个回答
0
投票

您是对的,任何释放操作都可以引导释放序列,无论是普通存储还是 RMW 存储。 你的分析是正确的,(#1,#2,#3,#4)是以#1为首的释放序列,#4读取#3写入的值。

但是,您不能断定#1 与#4 同步,因为#4 不是acquire。 请记住,在尝试从获取/释放操作获得同步时,我们总是诉诸atomics.order p2:

原子操作 A 对原子对象 M 执行释放操作,与原子同步 操作 B 对 M 执行获取操作并从释放中的任何副作用中获取其值 以 A 为首的序列。

通过取 A = #1、M =

v
、B = #4 和“释放序列中的任何副作用”= #3 来满足此定义的每个元素,但 B = #4 不执行获取操作。 所以我们不能得出#1 与#4 同步的结论。 因此,t1 中
x
的存储不会发生在 t3 中
x
加载之前,反之亦然。 因此该程序包含数据竞争,并且其行为是未定义的;特别是,断言可能会失败。

#4 本身就是以 #1 为首的发布序列的一个元素,这一事实与此分析无关。 (事实上,它是 RMW 的事实与该程序无关,因为存储的值

10
从未被加载。)

不难看出程序在典型实现的实践中如何失败:由于 #4 是宽松的,编译器和/或 CPU 可以自由地使用以下负载

x
对其进行重新排序。 因此,来自
x
的加载可能会在
v
中看到更新值之前很久就发生。 特别是,没有什么可以阻止它在从
x
存储到
t1
之前发生,因此可以加载旧值
0

如果您将 #4 升级到

memory_order_acquire
或更强,那么您确实可以得出结论:该程序具有明确定义的行为,并且断言不会失败。 (#4 执行 RMW 仍然无关紧要;如果它是普通获取负载,结论将是相同的。)

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