【问题标题】:Atomic behavior of unary increment operators一元增量运算符的原子行为
【发布时间】:2019-05-18 19:32:09
【问题描述】:

在某处我读到一元运算符本质上是原子的,因此它们可以在多线程环境中使用。为了确认这一点,我编写了两个单独的程序,其中

  1. 我使用了一个变量 x 并使用一元运算符 ++x 递增
  2. 我使用了变量 x 并使用 x=x+1 递增

我对比了两个程序的反汇编,没有发现差异。请提供您对此的意见。

【问题讨论】:

  • 仅仅因为++x 是原子的并不意味着x=x+1 不是吗? (从逻辑的角度来看,我怀疑++x 始终是原子的)
  • 给定一个变量可以驻留在内存中,因此需要多个指令(加载、添加、存储),它们不会自动成为原子
  • 我怀疑无论你在哪里读到,它所使用的“原子”一词的含义都与并发上下文中使用的含义不同。要么,要么他们完全错了,你不应该相信你在那里看到的任何东西。
  • std::atomic 将由了解您所针对的硬件的人实施,可能比您做得更好。让专家做这项工作。当你需要原子性时使用std::atomic
  • @chux 问题说的是“一元运算符”,而不是带有连字符和斜体的“一元运算符”,暗示了 C/C++ 语法特定的含义。后缀 operator++ 也不是 unary-expression 的一部分,而是 postfix-expression 的一部分。一元运算符是一个通用的、被广泛理解的术语,在这里可以正确使用。

标签: c++ c increment atomic


【解决方案1】:

在某处我读到一元运算符本质上是原子的,因此它们可以在多线程环境中使用。

那个来源是完全错误的。您需要使用 std::atomic(或 C 等效项)来实现原子性——一元操作并不特殊。


我对比了两个程序的反汇编,发现没有区别

这并不意味着生成的操作是原子的。没有区别,因为任何体面的编译器都会将x=x+1++x 优化到同一个程序集中(假设是内置类型)。

【讨论】:

  • 比较程序集实际上可能会证实相反的情况。如果您知道i = i + 1 的程序集是非原子的,那么对于i++,相同的程序集是非原子的,因此后者不是原子的。
【解决方案2】:

一元运算符的原子行为

在 C 中,前/后修复 ++ 不是 一元运算符,例如 & * + - ~ !。而是一元表达式的一部分。所以题名与正文不一致。

即使像 + 这样的一元运算符也不是原子的,因为对对象的访问(想想 long long)需要读取多个操作码。

【讨论】:

    【解决方案3】:

    您没有指定 x 的类型。

    1. 如果平台是 16 位或 8 位时 x 是 32 位整数,则“x++”操作 肯定会做多次手术
    2. x 甚至可以不是基本类型,x 可以是 Class 的一个实例,其中 operator++ 做了更复杂的事情,然后只是增加整数

    【讨论】:

    • “操作肯定会进行多次操作” - 我会小心做出这样的断言。您可以在 16 位架构上使用 32 位寄存器,并且您只需要一个总线锁,然后是一个增量,x++ 就可以是原子的。
    • 我同意你的观点,这取决于正在处理的对象。我使用了 32 位整数来简化它。但我应该使用 64 位整数,因为我的机器是 64 位的,所以操作系统(Windows 10)
    【解决方案4】:

    你对生成的代码做一个假设,如果只生成一条指令是的,它将是原子的,否则不是。

    在您的情况下,这假设目标处理器具有指令 inc <address>,并且编译器将生成它。

    【讨论】:

      【解决方案5】:

      在编写跨平台 C++ 时,只有在使用 std::atomic<> 时才会有原子行为。

      确实,在某些平台上,例如 Intel 64 位,处理器保证 inc 是原子的。但是,请不要编写依赖于此的代码!作为您未来的调试器,我想知道哪些数据打算通过线程共享,哪些不是。

      使用std::atomic<int> 可能需要编写更多工作,但是,它确实可以通过回退到平台要求 (std::atomic::is_lock_free) 或通过显式加锁来保证一切都以原子方式运行(在每个平台上)访问周围。它还插入保护以确保其他处理器内核的缓存无效(如果平台需要这样做)。

      在英特尔 64 位的实践中,这应该为您提供相同的程序集,如果没有,请在您的编译器上记录一个错误。

      同时,一些带有整数的操作可能不是原子的(operator*=),std::atomic 根本不包含这些操作,需要您正确处理这些操作。

      附带说明:++xx = x+1 是不同的操作,它们可能会针对相同的程序集进行优化。鉴于非原子平台要求,第二个突然是一个需要数天才能解决的错误。

      【讨论】:

        【解决方案6】:

        stronglr 操作的原子性取决于目标系统。一元操作在 RISC 微控制器等 RMW 系统上可能不是原子操作。

        这个问题没有单一的通用答案。

        【讨论】:

        • 很好的答案(虽然有点简洁),因为它没有声称它永远不可能是原子的(就像其他人一样)。但是:这个问题有一个“单一的通用答案”:“不,一元运算符不是原子本质上”。
        • @Ctx 它取决于抽象级别。如果我们从实现和硬件中抽象出来——是的。如果我们不 - 不。
        • @P__J__ C language is specified in terms of an abstract machine:“本国际标准中的语义描述描述了与优化问题无关的抽象机器的行为。”单一的通用答案“一元运算符不是原子的”。
        • @AndrewHenle 你是对的 - 但在编程实践中,许多程序员(尤其是那些非常接近硬件的程序员)必须考虑实现和硬件。简单的例子——如果你有一个时间关键的函数并且你不能禁用中断怎么办?在这种情况下,stdatomic 将无济于事。
        【解决方案7】:

        不正确。就算是,那https://en.cppreference.com/w/cpp/atomic/atomic#Type_aliases又有什么理由呢?

        我认为他们可能的意思是,对此类操作的计算通常非常微小,因此很可能永远不会出现竞争条件,这在实时代码中大多数情况下都是如此,您不会同时在 4 个 for 循环中计算 x++。

        【讨论】:

          【解决方案8】:

          一元运算符必然是原子的断言是一个神话。

          例如,++x 需要对 x 进行读写操作,这样就有可能发生数据竞争。

          ++x 编译为与x = x + 1 相同的代码这一事实无关紧要。

          如果您想避免数据争用,请使用原子类型,如果没有合适的原子类型可用,则使用互斥单元。为免生疑问,int 不一定是原子类型。

          【讨论】:

            【解决方案9】:

            我在某处读到一元运算符本质上是原子的,因此它们 可以在多线程环境中使用。

            这是错误的。例如x++ 需要x 的加载、x 的添加和存储。这些指令本质上不是原子的。

            【讨论】:

              猜你喜欢
              • 2021-05-08
              • 1970-01-01
              • 1970-01-01
              • 2020-07-14
              • 2012-12-04
              • 1970-01-01
              • 2014-03-31
              • 2016-12-21
              • 1970-01-01
              相关资源
              最近更新 更多