代码之家  ›  专栏  ›  技术社区  ›  Eric Auld

“与同步”关系

  •  0
  • Eric Auld  · 技术社区  · 7 月前

    来自cppreference article std::memory_order :

    与同步 :如果线程A中的原子存储是释放操作,线程B中来自同一变量的原子加载是获取操作,并且线程B中的加载读取线程A中存储写入的值,则线程A中储存与线程B中加载同步。

    “读取由写入的值”是否有我缺少的精确含义?

    1 回复  |  直到 7 月前
        1
  •  2
  •   user17732522    7 月前

    每个原子物体(单独地)都有其修改的总顺序,即所谓的 修改令 ,所有线程必须在全球范围内达成一致。

    原子对象上的每个副作用(即存储)都构成了此修改顺序中的一个条目。

    原子对象上的每个值计算(即加载) 接受其价值 除了一些一致性规则外,这种修改顺序中的一个副作用是未指明的。

    这些一致性规则在 cppreference page 您链接了:

    所有原子操作都保证满足以下四个要求:

    1. 写写一致性:如果计算A修改了一些原子M (a写入)发生在修改M的求值B之前,然后出现a 在M的修改顺序中早于B。

    2. 读-读一致性:如果某个原子M(a 读取)发生在M上的值计算B之前,如果a的值 来自M上的写X,那么B的值要么是 由X存储,或由M上出现的副作用Y存储的值 在M的修改顺序中晚于X。

    3. 读写一致性:如果某个原子M(a 读取)发生在M上的操作B(写入)之前,则 A来自一个副作用(写)X,它比B更早出现 M的修改顺序。

    4. 写读一致性:如果原子上有副作用(写)X 对象M发生在M的值计算(读取)B之前,然后 评估B应从X或副作用Y中取值 在M的修改顺序中跟随X。

    请注意,上面使用的是非正式语言,如“ A的值来自 “,正式应该是” A的值计算从 “.正式规范见 [intro.races]/14 以及以下段落。

    " 线程B中的负载读取线程a中存储器写入的值 “也是一种不太正式的表达方式” 线程B中的值计算按照原子对象的修改顺序从线程A中的副作用中获取其值 ".

    你的报价是说 与同步 关系仅发生在对原子对象产生副作用的特定评估中,从该评估中计算值 接受其价值 .

    这特别意味着,如果修改顺序中有两个副作用,都将相同的值存储到原子中,那么考虑到上述一致性规则,原子上的值计算可能会 接受它的价值 来自两者中的任何一个。虽然通过查看负载产生的值无法区分这两种情况,但它仍然会影响 与同步 关系以及内存同步保证您将获得什么。


    正如@PeterCodes在这个答案下的评论中指出的那样,发布/获取的规则意味着 与同步 引用cppreference也是不完整的。事实上 与同步 这种关系不仅与导致价值计算产生副作用的评估建立关系,而且与价值计算产生的副作用建立关系。

    相反,如果值计算从所谓的 发布序列由以下人员领导 所考虑的释放操作是由从所考虑的发布操作开始的释放操作组成的修改顺序的连续子序列。

    这意味着,如果该值是从修改顺序中的某个释放操作中获取的,并且在它之前还有另一个释放操作,那么通常获取操作也是 与同步 之前的发布操作。

    但由于一些不常见的CPU内存型号,这不适用于 全部的 之前的发布操作。当前的定义 释放顺序 可以在以下网址找到 [intro.races]/5 并且有(正在进行的?)关于它的讨论。具体来说,当前的定义只包含释放操作,这些操作也是读-修改-写操作。

    天真的人可能会期望 与同步 与修改顺序中所有先前版本存储的应用关系,因此这可能会导致一些令人惊讶的结果。

    例如,考虑以下场景:

    • 原子变量以值开头 0
    • 线程1发布商店 1 在写入一些数据A后发布
    • 线程2自旋等待原子变量的状态变为 1. 使用宽松的内存排序,然后(在其他地方)写入一些其他数据B以发布和释放存储 2 原子变量(不是RMW增量!)。
    • 线程3获取并加载该值 2. 来自thread 2的商店

    然后线程3可以安全地检索数据B,但由于非RMW修改不是发布序列的一部分,因此不会与线程1进行任何同步,访问数据A将是一场数据竞赛。