代码之家  ›  专栏  ›  技术社区  ›  dfeuer

获取和添加顺序

  •  2
  • dfeuer  · 技术社区  · 6 年前

    我正在为“稳定指针”替换分配系统 运行时系统,并且我正在超越我对并发编程理解的极限。

    假设变量包含0。线程A使用 __atomic_fetch_and_add 增加变量并以某种方式通知线程B。作为响应,线程B使用 _原子获取和添加 将同一变量递减,使其返回到0。所以看起来变量应该从0到1再回到0。是否保证另一个线程c不会看到按照从0到-1和从0到-1的相反顺序执行的添加?

    1 回复  |  直到 6 年前
        1
  •  1
  •   dho    6 年前

    我刚刚重新阅读了这个问题,添加了一些额外的说明,并意识到我假设了c11,而您的问题似乎是使用编译器内置的。从这个角度来看,如果你 memorder 使用是 __ATOMIC_SEQ_CST ,在任何情况下,都不能因为下面我详细说明的原因(从c11开始)观察到-1的值。

    TL;医生:这要看情况,但你必须真的开枪打自己的脚,才能保证这种行为。下面解释为什么 能够 发生了, 怎样 这可能会发生,为什么你不太可能遇到这种情况。

    原子操作保证具有全局顺序,但未定义全局总顺序。根据C11草案,§5.1.2.4P7:

    对特定原子对象的所有修改 以某种特定的总顺序出现,称为 修改令 属于

    根据这种修改的定义 其他线程观察到的总顺序是A/B,但也允许B/A。这实际上会产生外部观察者注意到值在-1和0之间转换的效果(假设是有符号的原子类型)。

    为了解决这个问题,标准定义了 同步操作 (同一节第5段):

    同步操作 在一个或多个内存位置上是 获取操作 A 释放操作 ,都是 获取和释放操作 ,或者 消费操作 .

    稍后,阅读这些操作如何组合以引入最终产生“先于发生”排序的依赖项的定义会有些乏味。我将省略这些;§5.1.2.4p14-22描述对某些对象的副作用的可观察性以及依赖如何影响这些副作用;§7.17.3描述控制这些依赖的api。

    如果不讨论这些部分,希望这些部分足以说明,它们确实允许观察者看到所描述的“相反顺序”。当你使用 atomic_fetch_add_explicit 用一个 memory_order_relaxed 参数,并且您的负载实现为 atomic_load_explicit 具有相同的松弛内存排序要求。在这种情况下,没有定义“happenebefore”关系,并且允许系统允许线程c按任意顺序观察修改。

    这不太可能是你真正会做的。首先,这是更多的打字。其次,api的命名和使用实际上表明,如果你想使用它,你应该知道你在做什么。这就是我的意思,你真的要开枪打自己的脚:你不愿意做这种事。

    如果你完全用 atomic_fetch_add , atomic_fetch_sub atomic_load (正如你可能会做的那样),你会没事的;§7.17.1P5中的标准规定:

    函数不以 _明确的 具有与 相应的 _明确的 功能 存储顺序 对于 记忆顺序 争论。

    标准保证这个顺序将携带数据依赖关系,这样从线程A的写入被认为“发生在”从线程B的写入之前。因此,一个观察器C,其自身的一致的内存顺序要求,被保证看到操作以顺序D交错按预期描述。

    上面都说:如果你能用C11,就用 ++ , -- = ,你会没事的。根据6.5.16.2p3, += -= 对原子类型的操作定义为使用store with memory_order_seq_cst . 根据第6.5.3p2条, ++ —— 运算符类似于 x+=1 x-=1 表达。简单赋值(6.5.16.2)指定lhs或rhs可以是原子类型,但不指定内存顺序。 Jens Gustedt says 在上面做手术 _Atomic -限定对象保证具有顺序一致性。我只能从脚注113中推测,脚注并不规范。但我认为这并不重要:如果所有的写操作都是一致的,那么任何读操作都必须从总的顺序中观察到一个有效的前一状态,该顺序从不包含-1。